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.


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ê.


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:
}

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"}

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"}
    }
}

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

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.