Serialización personalizada Jackson JSON para ciertos campos

93

¿Hay alguna forma de utilizar el procesador Jackson JSON para realizar una serialización de nivel de campo personalizada? Por ejemplo, me gustaría tener la clase

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

serializado al siguiente JSON:

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

Tenga en cuenta que age = 25 está codificado como un número, mientras que favoriteNumber = 123 está codificado como una cadena . Fuera de la caja Jackson se dirige inta un número. En este caso, quiero que favoriteNumber se codifique como una cadena.

Steve Kuo
fuente
1
Escribí una publicación sobre Cómo escribir un serializador personalizado con Jackson que puede ser útil para algunos.
Sam Berry

Respuestas:

106

Puede implementar un serializador personalizado de la siguiente manera:

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 debería manejar el autoboxing de inta Integerpor usted.

Kevin Bowersox
fuente
3
Jackson-databind (al menos 2.1.3) ya contiene ToStringSerializer especial, vea mi respuesta.
werupokz
@KevinBowersox ¿Pueden ayudarme con mi problema de deserialización, por favor?
JJD
¿Hay alguna forma menos terrible de hacer esto? Al igual que Person implements ToJson?
jameshfisher
1
En mi caso, incluso falló por as=String.classparte, debido a los tipos que usé. @ kevin-bowersox, sugiero actualizar su comentario, de acuerdo con lo que dijo @GarethLatty.
Bert
54

Jackson-databind (al menos 2.1.3) proporciona especial ToStringSerializer( com.fasterxml.jackson.databind.ser.std.ToStringSerializer)

Ejemplo:

public class Person {
    public String name;
    public int age;
    @JsonSerialize(using = ToStringSerializer.class)
    public int favoriteNumber:
}
Werupokz
fuente
3
¿Qué pasa con lo contrario, donde una cadena debe convertirse en un int? No veo ToIntSerializer.class.
jEremyB
@jEremyB Puede que tenga que escribir un deserializador personalizado
Drew Stephens
ToStringSerializer funciona pero FloatSerializer trae este mensaje: No se pudo escribir el contenido: java.lang.Integer no se puede convertir en java.lang.Float
Arnie Schwarzvogel
12

jackson-annotations proporciona @JsonFormatque puede manejar muchas personalizaciones sin la necesidad de escribir el serializador personalizado.

Por ejemplo, solicitar una STRINGforma para un campo con tipo numérico generará el valor numérico como cadena

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

resultará en la salida deseada

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

Agregue un @JsonPropertycaptador anotado, que devuelve un String, para el 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
fuente
7

En caso de que no desee contaminar su modelo con anotaciones y desee realizar algunas operaciones personalizadas, puede usar mixins.

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

Edad de anulación:

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

Haz lo que necesites con la edad:

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.
fuente
2

con la ayuda de @JsonView podemos decidir campos de clases modelo para serializar que satisfagan los criterios mínimos (tenemos que definir los criterios) como si pudiéramos tener una clase central con 10 propiedades pero solo se pueden serializar 5 propiedades que son necesarias para el cliente solamente

Defina nuestras Vistas simplemente creando la siguiente clase:

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

Clase de modelo anotada con 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
...
..
}

Ahora tenemos que escribir un convertidor json personalizado simplemente extendiendo la clase HttpMessageConverter desde 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);
        }
    }

}

Ahora es necesario decirle a Spring que use esta conversión json personalizada simplemente colocándola en 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>

Así es como podrá decidir qué campos serializar.

Chetan Pardeshi
fuente