Różne nazwy właściwości JSON podczas serializacji i deserializacji

149

Czy jest możliwe: mieć jedno pole w klasie, ale różne nazwy dla niego podczas serializacji / deserializacji w bibliotece Jacksona?

Na przykład mam klasę „Coordiantes”.

class Coordinates{
  int red;
}

W przypadku deserializacji z JSON chcesz mieć taki format:

{
  "red":12
}

Ale kiedy zserializuję obiekt, wynik powinien być taki:

{
  "r":12
}

Próbowałem to zaimplementować, stosując @JsonPropertyadnotacje zarówno na getter, jak i setter (z różnymi wartościami):

class Coordiantes{
    int red;

    @JsonProperty("r")
    public byte getRed() {
      return red;
    }

    @JsonProperty("red")
    public void setRed(byte red) {
      this.red = red;
    }
}

ale mam wyjątek:

org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Nierozpoznane pole „czerwone”

kiRach
źródło

Odpowiedzi:

203

Właśnie przetestowane i to działa:

public class Coordinates {
    byte red;

    @JsonProperty("r")
    public byte getR() {
      return red;
    }

    @JsonProperty("red")
    public void setRed(byte red) {
      this.red = red;
    }
}

Pomysł polega na tym, że nazwy metod powinny być różne, więc Jackson analizuje je jako różne pola, a nie jako jedno pole.

Oto kod testowy:

Coordinates c = new Coordinates();
c.setRed((byte) 5);

ObjectMapper mapper = new ObjectMapper();
System.out.println("Serialization: " + mapper.writeValueAsString(c));

Coordinates r = mapper.readValue("{\"red\":25}",Coordinates.class);
System.out.println("Deserialization: " + r.getR());

Wynik:

Serialization: {"r":5}
Deserialization: 25
bezmax
źródło
czy to samo jest możliwe z jaxb?
Cui Pengfei 崔鹏飞
38

Możesz użyć tego, @jsonAliasktóry został wprowadzony w Jackson 2.9.0

Przykład:

public class Info {
  @JsonAlias({ "red" })
  public String r;
}

Używa rpodczas serializacji, ale zezwala redna alias podczas deserializacji. To jednak nadal pozwala rna deserializację.

Asura
źródło
8
Dokumentacja @JsonAlias wyraźnie stwierdza, że has no effect during serialization where primary name is always used. Nie tego chce PO.
Xaero Degreaz
3
@XaeroDegreaz Domyślam się, że @Asura oznacza, że ​​możesz użyć rjako nazwy podstawowej, ale reddla @JsonAlias, która pozwala na serializację r, ale dodaje, redaby być rozpoznawanym podczas deserializacji. @JsonProperty("r")Dodanie do niego adnotacji i dodatkowo @JsonAlias("red")powinno działać dobrze w przypadku danego problemu.
Jerrot
16

Możesz użyć kombinacji @JsonSetter i @JsonGetter, aby odpowiednio kontrolować deserializację i serializację właściwości. Umożliwi to również zachowanie standardowych nazw metod pobierających i ustawiających, które odpowiadają rzeczywistej nazwie pola.

import com.fasterxml.jackson.annotation.JsonSetter;    
import com.fasterxml.jackson.annotation.JsonGetter;

class Coordinates {
    private int red;

    //# Used during serialization
    @JsonGetter("r")
    public int getRed() {
        return red;
    }

    //# Used during deserialization
    @JsonSetter("red")
    public void setRed(int red) {
        this.red = red;
    }
}
Xaero Degreaz
źródło
15

Powiązałbym dwie różne pary pobierające / ustawiające do jednej zmiennej:

class Coordinates{
    int red;

    @JsonProperty("red")
    public byte getRed() {
      return red;
    }

    public void setRed(byte red) {
      this.red = red;
    }

    @JsonProperty("r")
    public byte getR() {
      return red;
    }

    public void setR(byte red) {
      this.red = red;
    }
}
DRCB
źródło
13
Ale w tym przypadku podczas serializacji otrzymamy obie właściwości: „r” i „red”, o tych samych wartościach.
kiRach
@JsonIgnore na metodach, których nie chcesz przetwarzać, naprawi ten problem
Stephan
Obecnie istnieją wygodniejsze adnotacje: @JsonGetteri @JsonSetter. Można więc dokładnie ustawić, jak będzie się zachowywał serializator.
DRCB
6

Możliwe jest posiadanie normalnej pary getter / setter. Musisz tylko określić tryb dostępu w@JsonProperty

Oto test jednostkowy:

public class JsonPropertyTest {

  private static class TestJackson {

    private String color;

    @JsonProperty(value = "device_color", access = JsonProperty.Access.READ_ONLY)
    public String getColor() {
      return color;
    };

    @JsonProperty(value = "color", access = JsonProperty.Access.WRITE_ONLY)
    public void setColor(String color) {
      this.color = color;
    }

  }

  @Test
  public void shouldParseWithAccessModeSpecified() throws Exception {
    String colorJson = "{\"color\":\"red\"}";
    ObjectMapper mapper = new ObjectMapper();
    TestJackson colotObject = mapper.readValue(colorJson, TestJackson.class);

    String ser = mapper.writeValueAsString(colotObject);
    System.out.println("Serialized colotObject: " + ser);
  }
}

Otrzymałem wynik w następujący sposób:

Serialized colotObject: {"device_color":"red"}
Raman Yelianevich
źródło
5

Nie tego oczekiwałem jako rozwiązania (chociaż jest to uzasadniony przypadek użycia). Moim wymaganiem było zezwolenie istniejącemu błędnemu klientowi (już wydanej aplikacji mobilnej) na używanie alternatywnych nazw.

Rozwiązaniem jest zapewnienie oddzielnej metody ustawiającej, takiej jak ta:

@JsonSetter( "r" )
public void alternateSetRed( byte red ) {
    this.red = red;
}
Andy Talkowski
źródło
2

Wiem, że to stare pytanie, ale dla mnie działało, gdy zorientowałem się, że jest to sprzeczne z biblioteką Gson, więc jeśli używasz Gson, użyj @SerializedName("name")zamiast @JsonProperty("name")mieć nadzieję, że to pomaga

Khaled
źródło
2

Adnotacje, @JsonAliasktóre zostały wprowadzone w Jackson 2.9+, bez wspominania @JsonPropertyo elemencie do deserializacji z więcej niż jednym aliasem (różne nazwy dla właściwości JSON) działają dobrze.

Użyłem com.fasterxml.jackson.annotation.JsonAliasdo spójności pakietu z com.fasterxml.jackson.databind.ObjectMapperdla mojego przypadku użycia.

Na przykład:

@Data
@Builder
public class Chair {

    @JsonAlias({"woodenChair", "steelChair"})
    private String entityType;

}


@Test
public void test1() {

   String str1 = "{\"woodenChair\":\"chair made of wood\"}";
   System.out.println( mapper.readValue(str1, Chair.class));
   String str2 = "{\"steelChair\":\"chair made of steel\"}";
   System.out.println( mapper.readValue(str2, Chair.class));

}

po prostu działa dobrze.

Arnab Das
źródło
1

Musieli uwzględnić to jako funkcję, ponieważ teraz ustawienie innego @JsonPropertydla metody pobierającej i ustawiającej daje dokładnie to, czego można oczekiwać (inna nazwa właściwości podczas serializacji i deserializacji dla tego samego pola). Jackson w wersji 2.6.7

fetta
źródło
0

Możesz napisać klasę serializacji, aby to zrobić:

public class Symbol

{
     private String symbol;

     private String name;

     public String getSymbol() {
        return symbol;
    }
    public void setSymbol(String symbol) {
        this.symbol = symbol;
    }    
    public String getName() {
        return name;
    }    
    public void setName(String name) {
        this.name = name;
    }
}
public class SymbolJsonSerializer extends JsonSerializer<Symbol> {

    @Override
    public void serialize(Symbol symbol, JsonGenerator jgen, SerializerProvider serializers) throws IOException, JsonProcessingException {
        jgen.writeStartObject();

        jgen.writeStringField("symbol", symbol.getSymbol());
         //Changed name to full_name as the field name of Json string
        jgen.writeStringField("full_name", symbol.getName());
        jgen.writeEndObject(); 
    }
}
            ObjectMapper mapper = new ObjectMapper();

            SimpleModule module = new SimpleModule();
            module.addSerializer(Symbol.class, new SymbolJsonSerializer());
            mapper.registerModule(module); 

            //only convert non-null field, option...
            mapper.setSerializationInclusion(Include.NON_NULL); 

            String jsonString = mapper.writeValueAsString(symbolList);
Vernon Kujyio
źródło