Serialização personalizada Jackson JSON para determinados campos

93

Existe uma maneira de usar o processador Jackson JSON para fazer a serialização em nível de campo personalizado? Por exemplo, eu gostaria de ter a aula

public class Person {
    public String name;
    public int age;
    public int favoriteNumber;
}

serializado para o seguinte JSON:

{ "name": "Joe", "age": 25, "favoriteNumber": "123" }

Observe que age = 25 é codificado como um número, enquanto favoriteNumber = 123 é codificado como uma string . Fora da caixa, Jackson comanda intum número. Neste caso, quero que favoriteNumber seja codificado como uma string.

Steve Kuo
fonte
1
Eu escrevi um post sobre como escrever um serializador personalizado com Jackson que pode ser útil para alguns.
Sam Berry

Respostas:

106

Você pode implementar um serializador personalizado da seguinte maneira:

public class Person {
    public String name;
    public int age;
    @JsonSerialize(using = IntToStringSerializer.class, as=String.class)
    public int favoriteNumber:
}


public class IntToStringSerializer extends JsonSerializer<Integer> {

    @Override
    public void serialize(Integer tmpInt, 
                          JsonGenerator jsonGenerator, 
                          SerializerProvider serializerProvider) 
                          throws IOException, JsonProcessingException {
        jsonGenerator.writeObject(tmpInt.toString());
    }
}

Java deve lidar com o autoboxing de intpara Integerpara você.

Kevin Bowersox
fonte
3
Jackson-databind (pelo menos 2.1.3) já contém ToStringSerializer especial, veja minha resposta.
werupokz
@KevinBowersox Você pode me ajudar com meu problema de desserialização, por favor?
JJD
Existe alguma maneira menos terrível de fazer isso? Gostou Person implements ToJson?
jameshfisher,
1
No meu caso até falhou na as=String.classpeça, pelos tipos que usei. @kevin-bowersox, sugiro atualizar seu comentário, de acordo com o que @GarethLatty disse.
Bert,
54

Jackson-databind (pelo menos 2.1.3) fornece especial ToStringSerializer( com.fasterxml.jackson.databind.ser.std.ToStringSerializer)

Exemplo:

public class Person {
    public String name;
    public int age;
    @JsonSerialize(using = ToStringSerializer.class)
    public int favoriteNumber:
}
werupokz
fonte
3
E quanto ao reverso, onde uma String precisa ser convertida em um int? Não vejo ToIntSerializer.class.
jEremyB de
@jEremyB Você pode ter que escrever um desserializador personalizado
Drew Stephens
ToStringSerializer funciona, mas FloatSerializer traz esta mensagem: Não foi possível escrever o conteúdo: java.lang.Integer não pode ser convertido para java.lang.Float
Arnie Schwarzvogel
12

jackson-annotations fornece o @JsonFormatque pode lidar com muitas personalizações sem a necessidade de escrever o serializador personalizado.

Por exemplo, solicitar uma STRINGforma para um campo com tipo numérico resultará no valor numérico como string

public class Person {
    public String name;
    public int age;
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    public int favoriteNumber;
}

resultará na saída desejada

{"name":"Joe","age":25,"favoriteNumber":"123"}
Oleg Estekhin
fonte
11

Adicione um @JsonPropertygetter anotado, que retorna um String, para o favoriteNumbercampo:

public class Person {
    public String name;
    public int age;
    private int favoriteNumber;

    public Person(String name, int age, int favoriteNumber) {
        this.name = name;
        this.age = age;
        this.favoriteNumber = favoriteNumber;
    }

    @JsonProperty
    public String getFavoriteNumber() {
        return String.valueOf(favoriteNumber);
    }

    public static void main(String... args) throws Exception {
        Person p = new Person("Joe", 25, 123);
        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.writeValueAsString(p)); 
        // {"name":"Joe","age":25,"favoriteNumber":"123"}
    }
}
João silva
fonte
7

Caso você não queira poluir seu modelo com anotações e queira realizar algumas operações customizadas, você pode usar mixins.

ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.setMixInAnnotation(Person.class, PersonMixin.class);
mapper.registerModule(simpleModule);

Substituir idade:

public abstract class PersonMixin {
    @JsonSerialize(using = PersonAgeSerializer.class)
    public String age;
}

Faça o que for preciso com a idade:

public class PersonAgeSerializer extends JsonSerializer<Integer> {
    @Override
    public void serialize(Integer integer, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(String.valueOf(integer * 52) + " months");
    }
}
Igor G.
fonte
2

com a ajuda de @JsonView podemos decidir campos de classes de modelo para serializar que satisfaçam os critérios mínimos (temos que definir os critérios) como podemos ter uma classe principal com 10 propriedades, mas apenas 5 propriedades podem ser serializadas que são necessárias para o cliente só

Defina nossas visualizações simplesmente criando a seguinte classe:

public class Views
{
    static class Android{};
    static class IOS{};
    static class Web{};
}

Classe de modelo anotada com vistas:

public class Demo 
{
    public Demo() 
    {
    }

@JsonView(Views.IOS.class)
private String iosField;

@JsonView(Views.Android.class)
private String androidField;

@JsonView(Views.Web.class)
private String webField;

 // getters/setters
...
..
}

Agora temos que escrever um conversor json personalizado simplesmente estendendo a classe HttpMessageConverter do spring como:

    public class CustomJacksonConverter implements HttpMessageConverter<Object> 
    {
    public CustomJacksonConverter() 
        {
            super();
        //this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.ClientView.class));
        this.delegate.getObjectMapper().configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
        this.delegate.getObjectMapper().setSerializationInclusion(Include.NON_NULL);

    }

    // a real message converter that will respond to methods and do the actual work
    private MappingJackson2HttpMessageConverter delegate = new MappingJackson2HttpMessageConverter();

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return delegate.canRead(clazz, mediaType);
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return delegate.canWrite(clazz, mediaType);
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return delegate.getSupportedMediaTypes();
    }

    @Override
    public Object read(Class<? extends Object> clazz,
            HttpInputMessage inputMessage) throws IOException,
            HttpMessageNotReadableException {
        return delegate.read(clazz, inputMessage);
    }

    @Override
    public void write(Object obj, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException 
    {
        synchronized(this) 
        {
            String userAgent = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("userAgent");
            if ( userAgent != null ) 
            {
                switch (userAgent) 
                {
                case "IOS" :
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.IOS.class));
                    break;
                case "Android" :
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.Android.class));
                    break;
                case "Web" :
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( Views.Web.class));
                    break;
                default:
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
                    break;
                }
            }
            else
            {
                // reset to default view
                this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
            }
            delegate.write(obj, contentType, outputMessage);
        }
    }

}

Agora é necessário dizer ao spring para usar este json convert personalizado simplesmente colocando-o em dispatcher-servlet.xml

<mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean id="jsonConverter" class="com.mactores.org.CustomJacksonConverter" >
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

É assim que você poderá decidir quais campos serão serializados.

Chetan Pardeshi
fonte