Nomes diferentes da propriedade JSON durante serialização e desserialização

149

É possível: ter um campo na classe, mas nomes diferentes para ele durante a serialização / desserialização na biblioteca Jackson?

Por exemplo, eu tenho a classe "Coordiantes".

class Coordinates{
  int red;
}

Para desserialização do JSON, queira ter um formato como este:

{
  "red":12
}

Mas quando eu serializar o objeto, o resultado deve ser como este:

{
  "r":12
}

Tentei implementar isso aplicando a @JsonPropertyanotação no getter e no setter (com valores diferentes):

class Coordiantes{
    int red;

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

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

mas eu tenho uma exceção:

org.codehaus.jackson.map.exc.UnrecognizedPropertyException: campo não reconhecido "vermelho"

kiRach
fonte

Respostas:

203

Acabei de testar e isso funciona:

public class Coordinates {
    byte red;

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

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

A ideia é que os nomes dos métodos sejam diferentes; portanto, jackson o analisa como campos diferentes, não como um campo.

Aqui está o código de teste:

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());

Resultado:

Serialization: {"r":5}
Deserialization: 25
bezmax
fonte
é o mesmo possível com jaxb?
Cui Pengfei # 22/14
38

Você pode usar o @jsonAliasque foi introduzido no jackson 2.9.0

Exemplo:

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

Isso é usado rdurante a serialização, mas permite redum alias durante a desserialização. Isso ainda permite rser desserializado também.

Asura
fonte
8
A documentação para @JsonAlias afirma explicitamente isso has no effect during serialization where primary name is always used. Não é isso que o OP quer.
Xaero Degreaz
3
@XaeroDegreaz Acho que @Asura significa que você pode usar rcomo nome principal, mas redpara o @JsonAlias, o que permite serializá-lo r, mas acrescenta reda ser reconhecido na desserialização. Anotá-lo com @JsonProperty("r")e adicionalmente @JsonAlias("red")deve funcionar bem para o problema em questão.
Jerrot 21/02/19
16

Você pode usar uma combinação de @JsonSetter e @JsonGetter para controlar a desserialização e serialização de sua propriedade, respectivamente. Isso também permitirá que você mantenha nomes padronizados de métodos getter e setter que correspondem ao seu nome de campo real.

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
fonte
15

Eu ligaria dois pares diferentes de getters / setters a uma variável:

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
fonte
13
Mas neste caso, durante a serialização, obteremos as duas propriedades: "r" e "red", com os mesmos valores.
kiRach
O @JsonIgnore sobre os métodos que você não deseja processar corrigirá esse problema
Stephan
6

É possível ter um par getter / setter normal. Você só precisa especificar o modo de acesso em@JsonProperty

Aqui está o teste de unidade para isso:

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);
  }
}

Eu obtive a saída da seguinte maneira:

Serialized colotObject: {"device_color":"red"}
Raman Yelianevich
fonte
5

Não era isso que eu esperava como solução (embora seja um caso de uso legítimo). Meu requisito era permitir que um cliente com bugs existente (um aplicativo móvel que já foi lançado) usasse nomes alternativos.

A solução está em fornecer um método de incubação separado como este:

@JsonSetter( "r" )
public void alternateSetRed( byte red ) {
    this.red = red;
}
Andy Talkowski
fonte
2

Eu sei que é uma pergunta antiga, mas para mim eu a fiz funcionar quando descobri que ela está em conflito com a biblioteca Gson; portanto, se você estiver usando o Gson, use em @SerializedName("name")vez de @JsonProperty("name")esperar que isso ajude

Khaled
fonte
2

Anotar com o @JsonAliasque foi introduzido com Jackson 2.9+, sem mencionar @JsonPropertyo item a ser desserializado com mais de um alias (nomes diferentes para uma propriedade json) funciona bem.

Eu usei com.fasterxml.jackson.annotation.JsonAliasa consistência do pacote com.fasterxml.jackson.databind.ObjectMapperno meu caso de uso.

Por exemplo:

@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));

}

apenas funciona bem.

Arnab Das
fonte
1

Eles devem ter incluído isso como um recurso, porque agora definir um diferente @JsonPropertypara um getter e setter resulta exatamente no que você esperaria (nome de propriedade diferente durante a serialização e desserialização para o mesmo campo). Jackson versão 2.6.7

fetta
fonte
0

Você pode escrever uma classe serializada para fazer isso:

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
fonte