tutorial serialize readvalue java serialization jackson

java - serialize - Jackson: serializador personalizado que reemplaza solo campos específicos



objectmapper java import (5)

El hecho de que no pueda modificar las clases NO significa que no podría usar anotaciones: solo use las anotaciones de mezcla. Vea this entrada de blog, por ejemplo (o busque más en Google con "anotaciones de mezcla de jackson") para saber cómo usar esto.

He utilizado específicamente a Jackson con clases generadas por protobuf y thrift, y funcionan bastante bien. Para versiones anteriores de Thrift, tuve que deshabilitar el descubrimiento de "is-setters", métodos que Thrift genera para ver si una propiedad específica se ha establecido explícitamente, pero de lo contrario las cosas funcionaron bien.

Sé cómo usar un serializador personalizado en Jackson (extendiendo JsonSerializer ), pero quiero que el serializador por defecto funcione en todos los campos, excepto en un solo campo, que deseo anular usando el serializador personalizado.

Las anotaciones no son una opción, porque estoy serializando una clase generada (de Thrift).

¿Cómo especifico solo ciertos campos que se anularán al escribir un serializador jackson personalizado?

Actualizar:

Aquí está la clase que quiero serializar:

class Student { int age; String firstName; String lastName; double average; int numSubjects // .. more such properties ... }

La clase anterior tiene muchas propiedades, la mayoría de las cuales utilizan tipos nativos. Solo quiero reemplazar algunas propiedades en el serializador personalizado y dejar que Jackson se ocupe del resto como de costumbre. Por ejemplo, solo quiero convertir el campo "edad" en una salida personalizada.


En caso de que no quiera contaminar su modelo con anotaciones, podría usar mixins.

ObjectMapper mapper = new ObjectMapper(); SimpleModule simpleModule = new SimpleModule(); simpleModule.setMixInAnnotation(Student.class, StudentMixin.class); mapper.registerModule(simpleModule);

Y desea anular el campo de identificación, por ejemplo:

public abstract class StudentMixin { @JsonSerialize(using = StudentIdSerializer.class) public String id; }

Haz lo que necesites con el campo:

public class StudentIdSerializer extends JsonSerializer<Integer> { @Override public void serialize(Integer integer, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeString(String.valueOf(integer * 2)); } }


Me enfrenté al mismo problema y lo resolví con CustomSerializerFactory .

Este enfoque le permite ignorar algún campo específico para todos los objetos o para tipos específicos.

public class EntityCustomSerializationFactory extends CustomSerializerFactory { //ignored fields private static final Set<String> IGNORED_FIELDS = new HashSet<String>( Arrays.asList( "class", "value", "some" ) ); public EntityCustomSerializationFactory() { super(); } public EntityCustomSerializationFactory(Config config) { super(config); } @Override protected void processViews(SerializationConfig config, BeanSerializerBuilder builder) { super.processViews(config, builder); //ignore fields only for concrete class //note, that you can avoid or change this check if (builder.getBeanDescription().getBeanClass().equals(Entity.class)){ //get original writer List<BeanPropertyWriter> originalWriters = builder.getProperties(); //create actual writers List<BeanPropertyWriter> writers = new ArrayList<BeanPropertyWriter>(); for (BeanPropertyWriter writer: originalWriters){ String propName = writer.getName(); //if it isn''t ignored field, add to actual writers list if (!IGNORED_FIELDS.contains(propName)){ writers.add(writer); } } builder.setProperties(writers); } } }

Y luego puedes usarlo de la siguiente manera:

objectMapper.setSerializerFactory(new EntityCustomSerializationFactory()); objectMapper.writeValueAsString(new Entity());//response will be without ignored fields


Suponiendo que tu clase objetivo es

public class Student { int age; String firstName; String lastName; double average; int numSubjects; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public double getAverage() { return average; } public void setAverage(double average) { this.average = average; } public int getNumSubjects() { return numSubjects; } public void setNumSubjects(int numSubjects) { this.numSubjects = numSubjects; } }

Necesita escribir un serializador personalizado como se indica a continuación

public class MyCustomSerializer extends JsonSerializer<Student> { @Override public void serialize(Student value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { if (value != null) { jgen.writeStartObject(); jgen.writeStringField("age", "Age: " + value.getAge()); //Here a custom way to render age field is used jgen.writeStringField("firstName", value.getFirstName()); jgen.writeStringField("lastName", value.getLastName()); jgen.writeNumberField("average", value.getAverage()); jgen.writeNumberField("numSubjects", value.getNumSubjects()); //Write other properties jgen.writeEndObject(); } } }

Luego agrégalo al ObjectMapper

ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule("custom", Version.unknownVersion()); module.addSerializer(Student.class, new MyCustomSerializer()); mapper.registerModule(module);

entonces úsalo como

Student s = new Student(); s.setAge(2); s.setAverage(3.4); s.setFirstName("first"); s.setLastName("last"); s.setNumSubjects(3); StringWriter sw = new StringWriter(); mapper.writeValue(sw, s); System.out.println(sw.toString());

Producirá ao / p como

{"age": "Age: 2", "firstName": "first", "lastName": "last", "average": 3.4, "numSubjects": 3}


con la ayuda de @JsonView podemos decidir campos de clases de modelo para serializar que cumplan con los criterios mínimos (tenemos que definir los criterios), como podemos tener una clase principal con 10 propiedades, pero solo 5 propiedades pueden ser serializadas 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 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 el convertidor json personalizado simplemente extendiendo la clase HttpMessageConverter desde la primavera 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 hay que decirle a Spring que use este convertidor json personalizado simplemente colocando esto 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 se serializarán.

Gracias