java - example - spring json serializer
¿Cómo uso un Serializador personalizado con Jackson? (10)
Como se mencionó, @JsonValue es una buena manera. Pero si no le importa un serializador personalizado, no es necesario escribir uno para el artículo, sino uno para el usuario; de ser así, sería tan simple como:
public void serialize(Item value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeNumber(id);
}
Otra posibilidad más es implementar JsonSerializable
, en cuyo caso no es necesario registrarse.
En cuanto al error; eso es raro, es probable que desee actualizar a una versión posterior. Pero también es más seguro extender org.codehaus.jackson.map.ser.SerializerBase
ya que tendrá implementaciones estándar de métodos no esenciales (es decir, todo menos la llamada de serialización real).
Tengo dos clases de Java que quiero serializar a JSON usando Jackson:
public class User {
public final int id;
public final String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
public class Item {
public final int id;
public final String itemNr;
public final User createdBy;
public Item(int id, String itemNr, User createdBy) {
this.id = id;
this.itemNr = itemNr;
this.createdBy = createdBy;
}
}
Quiero serializar un artículo a este JSON:
{"id":7, "itemNr":"TEST", "createdBy":3}
con el usuario serializado para incluir solo el id
. También seré capaz de serializar todos los objetos de usuario a JSON como:
{"id":3, "name": "Jonas", "email": "[email protected]"}
Así que supongo que necesito escribir un serializador personalizado para el Item
e intenté con esto:
public class ItemSerializer extends JsonSerializer<Item> {
@Override
public void serialize(Item value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.id);
jgen.writeNumberField("itemNr", value.itemNr);
jgen.writeNumberField("createdBy", value.user.id);
jgen.writeEndObject();
}
}
Serializo el JSON con este código de Jackson How-to: Serializadores personalizados :
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule",
new Version(1,0,0,null));
simpleModule.addSerializer(new ItemSerializer());
mapper.registerModule(simpleModule);
StringWriter writer = new StringWriter();
try {
mapper.writeValue(writer, myItem);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Pero me sale este error:
Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType() (use alternative registration method?)
at org.codehaus.jackson.map.module.SimpleSerializers.addSerializer(SimpleSerializers.java:62)
at org.codehaus.jackson.map.module.SimpleModule.addSerializer(SimpleModule.java:54)
at com.example.JsonTest.main(JsonTest.java:54)
¿Cómo puedo usar un Serializador personalizado con Jackson?
Así es como lo haría con Gson:
public class UserAdapter implements JsonSerializer<User> {
@Override
public JsonElement serialize(User src, java.lang.reflect.Type typeOfSrc,
JsonSerializationContext context) {
return new JsonPrimitive(src.id);
}
}
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(User.class, new UserAdapter());
Gson gson = builder.create();
String json = gson.toJson(myItem);
System.out.println("JSON: "+json);
Pero necesito hacerlo ahora con Jackson, ya que Gson no tiene soporte para interfaces.
En mi caso (Spring 3.2.4 y Jackson 2.3.1), configuración XML para el serializador personalizado:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="false">
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="serializers">
<array>
<bean class="com.example.business.serializer.json.CustomObjectSerializer"/>
</array>
</property>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
fue de forma inexplicable sobrescrito de nuevo al valor predeterminado por algo.
Esto funcionó para mí:
CustomObject.java
@JsonSerialize(using = CustomObjectSerializer.class)
public class CustomObject {
private Long value;
public Long getValue() {
return value;
}
public void setValue(Long value) {
this.value = value;
}
}
CustomObjectSerializer.java
public class CustomObjectSerializer extends JsonSerializer<CustomObject> {
@Override
public void serialize(CustomObject value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("y", value.getValue());
jgen.writeEndObject();
}
@Override
public Class<CustomObject> handledType() {
return CustomObject.class;
}
}
No se necesita ninguna configuración XML ( <mvc:message-converters>(...)</mvc:message-converters>
) en mi solución.
Escribí un ejemplo para una serialización / deserialización Timestamp.class
personalizada, pero podría usarlo para lo que quiera.
Al crear el asignador de objetos, haga algo como esto:
public class JsonUtils {
public static ObjectMapper objectMapper = null;
static {
objectMapper = new ObjectMapper();
SimpleModule s = new SimpleModule();
s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler());
s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler());
objectMapper.registerModule(s);
};
}
por ejemplo, en java ee
, podrías inicializarlo con esto:
import java.time.LocalDateTime;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
@Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {
private final ObjectMapper objectMapper;
public JacksonConfig() {
objectMapper = new ObjectMapper();
SimpleModule s = new SimpleModule();
s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler());
s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler());
objectMapper.registerModule(s);
};
@Override
public ObjectMapper getContext(Class<?> type) {
return objectMapper;
}
}
donde el serializador debería ser algo como esto:
import java.io.IOException;
import java.sql.Timestamp;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class TimestampSerializerTypeHandler extends JsonSerializer<Timestamp> {
@Override
public void serialize(Timestamp value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
String stringValue = value.toString();
if(stringValue != null && !stringValue.isEmpty() && !stringValue.equals("null")) {
jgen.writeString(stringValue);
} else {
jgen.writeNull();
}
}
@Override
public Class<Timestamp> handledType() {
return Timestamp.class;
}
}
y deserializador algo como esto:
import java.io.IOException;
import java.sql.Timestamp;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class TimestampDeserializerTypeHandler extends JsonDeserializer<Timestamp> {
@Override
public Timestamp deserialize(JsonParser jp, DeserializationContext ds) throws IOException, JsonProcessingException {
SqlTimestampConverter s = new SqlTimestampConverter();
String value = jp.getValueAsString();
if(value != null && !value.isEmpty() && !value.equals("null"))
return (Timestamp) s.convert(Timestamp.class, value);
return null;
}
@Override
public Class<Timestamp> handledType() {
return Timestamp.class;
}
}
Estos son patrones de comportamiento que noté al tratar de comprender la serialización de Jackson.
1) Supongamos que hay un objeto Classroom y una clase Student. Hice todo público y final para facilitar.
public class Classroom {
public final double double1 = 1234.5678;
public final Double Double1 = 91011.1213;
public final Student student1 = new Student();
}
public class Student {
public final double double2 = 1920.2122;
public final Double Double2 = 2324.2526;
}
2) Supongamos que estos son los serializadores que usamos para serializar los objetos en JSON. WriteObjectField utiliza el propio serializador del objeto si está registrado con el asignador de objetos; si no, lo serializa como un POJO. El campo writeNumberField solo acepta primitivas como argumentos.
public class ClassroomSerializer extends StdSerializer<Classroom> {
public ClassroomSerializer(Class<Classroom> t) {
super(t);
}
@Override
public void serialize(Classroom value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
jgen.writeStartObject();
jgen.writeObjectField("double1-Object", value.double1);
jgen.writeNumberField("double1-Number", value.double1);
jgen.writeObjectField("Double1-Object", value.Double1);
jgen.writeNumberField("Double1-Number", value.Double1);
jgen.writeObjectField("student1", value.student1);
jgen.writeEndObject();
}
}
public class StudentSerializer extends StdSerializer<Student> {
public StudentSerializer(Class<Student> t) {
super(t);
}
@Override
public void serialize(Student value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
jgen.writeStartObject();
jgen.writeObjectField("double2-Object", value.double2);
jgen.writeNumberField("double2-Number", value.double2);
jgen.writeObjectField("Double2-Object", value.Double2);
jgen.writeNumberField("Double2-Number", value.Double2);
jgen.writeEndObject();
}
}
3) Registre solo un DoubleSerializer con el patrón de salida DecimalFormat ###,##0.000
, en SimpleModule y la salida es:
{
"double1" : 1234.5678,
"Double1" : {
"value" : "91,011.121"
},
"student1" : {
"double2" : 1920.2122,
"Double2" : {
"value" : "2,324.253"
}
}
}
Puede ver que la serialización de POJO diferencia entre doble y doble, utilizando el DoubleSerialzer para dobles y el uso de un formato de cadena normal para dobles.
4) Registrar DoubleSerializer y ClassroomSerializer, sin el StudentSerializer. Esperamos que el resultado sea tal que si escribimos un doble como objeto, se comporta como un Doble, y si escribimos un Doble como un número, se comporta como un doble. La variable de instancia de Estudiante debe escribirse como un POJO y seguir el patrón anterior ya que no se registra.
{
"double1-Object" : {
"value" : "1,234.568"
},
"double1-Number" : 1234.5678,
"Double1-Object" : {
"value" : "91,011.121"
},
"Double1-Number" : 91011.1213,
"student1" : {
"double2" : 1920.2122,
"Double2" : {
"value" : "2,324.253"
}
}
}
5) Registrar todos los serializadores. El resultado es:
{
"double1-Object" : {
"value" : "1,234.568"
},
"double1-Number" : 1234.5678,
"Double1-Object" : {
"value" : "91,011.121"
},
"Double1-Number" : 91011.1213,
"student1" : {
"double2-Object" : {
"value" : "1,920.212"
},
"double2-Number" : 1920.2122,
"Double2-Object" : {
"value" : "2,324.253"
},
"Double2-Number" : 2324.2526
}
}
exactamente como se esperaba
Otra nota importante: si tiene múltiples serializadores para la misma clase registrada con el mismo Módulo, entonces el Módulo seleccionará el serializador para esa clase que se agregó más recientemente a la lista. Esto no debería usarse, es confuso y no estoy seguro de cuán consistente es esto
Moral: si desea personalizar la serialización de primitivas en su objeto, debe escribir su propio serializador para el objeto. No puede confiar en la serialización de POJO Jackson.
Intenté hacer esto también, y hay un error en el código de ejemplo en la página web de Jackson que no incluye el tipo (.class) en el método call to addSerializer, que debería decir así:
simpleModule.addSerializer(Item.class, new ItemSerializer());
En otras palabras, estas son las líneas que instancian el SimpleModule y agregan el serializador (con la línea incorrecta anterior comentada):
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule",
new Version(1,0,0,null));
// simpleModule.addSerializer(new ItemSerializer());
simpleModule.addSerializer(Item.class, new ItemSerializer());
mapper.registerModule(simpleModule);
FYI: Aquí está la referencia para el código de ejemplo correcto: http://wiki.fasterxml.com/JacksonFeatureModules
¡Espero que esto ayude!
Puede poner @JsonSerialize(using = CustomDateSerializer.class)
sobre cualquier campo de fecha del objeto que se serializará.
public class CustomDateSerializer extends SerializerBase<Date> {
public CustomDateSerializer() {
super(Date.class, true);
}
@Override
public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
SimpleDateFormat formatter = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss ''GMT''ZZZ (z)");
String format = formatter.format(value);
jgen.writeString(format);
}
}
Si su único requisito en su serializador personalizado es omitir la serialización del campo de name
del User
, márquelo como transitorio . Jackson no serializará ni deserializará campos transitorios .
[ver también: ¿Por qué Java tiene campos transitorios? ]
Tienes que anular el método handleType y todo funcionará
@Override
public Class<Item> handledType()
{
return Item.class;
}
Use @JsonValue:
public class User {
int id;
String name;
@JsonValue
public int getId() {
return id;
}
}
@JsonValue solo funciona en métodos, por lo que debe agregar el método getId. Debería poder omitir su serializador personalizado por completo.
Las Vistas JSON de Jackson podrían ser una forma más sencilla de cumplir sus requisitos, especialmente si tiene cierta flexibilidad en su formato JSON.
Si {"id":7, "itemNr":"TEST", "createdBy":{id:3}}
es una representación aceptable, entonces esto será muy fácil de lograr con muy poco código.
Simplemente debe anotar el campo de nombre del usuario como parte de una vista y especificar una vista diferente en su solicitud de serialización (los campos sin anotación se incluirían de forma predeterminada)
Por ejemplo: defina las vistas:
public class Views {
public static class BasicView{}
public static class CompleteUserView{}
}
Anota al usuario:
public class User {
public final int id;
@JsonView(Views.CompleteUserView.class)
public final String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
Y solicite en serie una vista que no contenga el campo que desea ocultar (los campos no anotados se serializan de manera predeterminada):
objectMapper.getSerializationConfig().withView(Views.BasicView.class);