Comment puis-je utiliser un Serializer personnalisé avec Jackson?
j'ai deux classes Java que je veux sérialiser à JSON en utilisant 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;
}
}
je veux sérialiser un article à ce JSON:
{"id":7, "itemNr":"TEST", "createdBy":3}
avec le numéro de série de L'utilisateur pour inclure seulement le id
. Je serai également capable de sériliser tous les objets utilisateur à JSON comme:
{"id":3, "name": "Jonas", "email": "jonas@example.com"}
donc je suppose que je dois écrire un serializer personnalisé pour Item
et essayé avec ceci:
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();
}
}
je sérialiser le JSON avec ce code de Jackson Comment: Personnalisé Sérialiseurs :
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();
}
mais je reçois cette erreur:
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)
Comment puis-je utiliser un Serializer personnalisé avec Jackson?
C'est la façon dont je le ferais avec 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);
mais je dois le faire avec Jackson maintenant, puisque Gson n'a pas soutien pour les interfaces.
10 réponses
comme mentionné, @JsonValue est un bon moyen. Mais si vous ne vous gênez pas d'un sérialiseur personnalisé, il n'y a pas besoin d'en écrire un pour L'article mais plutôt un pour L'utilisateur -- si c'est le cas, ce serait aussi simple que:
public void serialize(Item value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeNumber(id);
}
une autre possibilité encore est de mettre en œuvre JsonSerializable
, auquel cas aucun enregistrement n'est nécessaire.
Comme à l'erreur; c'est bizarre -- vous voulez probablement mettre à niveau vers une version ultérieure. Mais il est également plus sûr de prolonger org.codehaus.jackson.map.ser.SerializerBase
comme il aura implémentations standard de méthodes non essentielles (c.-à-d. tout sauf l'appel de sérialisation réel).
vous pouvez placer @JsonSerialize(using = CustomDateSerializer.class)
sur n'importe quel champ date de l'objet à sérialiser.
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);
}
}
j'ai essayé de le faire aussi, et il y a une erreur dans le code d'exemple sur la page Web de Jackson qui n'inclut pas le type (.class) dans la méthode call to addSerializer, qui devrait se lire comme ceci:
simpleModule.addSerializer(Item.class, new ItemSerializer());
en d'autres termes, ce sont les lignes qui instancient le simpleModule et ajouter le serializer (avec la ligne incorrecte antérieure commenté):
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: Voici la référence pour le code d'exemple correct: http://wiki.fasterxml.com/JacksonFeatureModules
Espérons que cette aide!
Use @JsonValue:
public class User {
int id;
String name;
@JsonValue
public int getId() {
return id;
}
}
@JsonValue ne fonctionne que sur les méthodes donc vous devez ajouter la méthode getId. Vous devriez être en mesure de sauter votre serializer personnalisé tout à fait.
ce sont des modèles de comportement que j'ai remarqué en essayant de comprendre la sérialisation de Jackson.
1) supposons qu'il y a une classe d'objets et un élève de classe. J'ai rendu tout public et définitif pour plus de facilité.
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) supposons que ce sont les sérialiseurs que nous utilisons pour sérialiser les objets dans JSON. Le champ writeobject utilise le propre serializer de l'objet s'il est enregistré avec le mapper d'objet; sinon, il sérialise comme un POJO. Le champ writeNumberField n'accepte que les primitifs comme arguments.
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) Enregistrer seulement un DoubleSerializer avec le modèle de sortie Décimalformat ###,##0.000
, dans SimpleModule et la sortie est:
{
"double1" : 1234.5678,
"Double1" : {
"value" : "91,011.121"
},
"student1" : {
"double2" : 1920.2122,
"Double2" : {
"value" : "2,324.253"
}
}
}
vous pouvez voir que la sérialisation POJO différencie entre double et Double, en utilisant le DoubleSerialzer pour les Doubles et en utilisant un format de chaîne régulière pour les doubles.
4) Registre DoubleSerializer et ClassroomSerializer, sans le StudentSerializer. Nous nous attendons à ce que la sortie est telle que si nous écrivons un double comme un objet, il se comporte comme un Double, et si nous écrivons un Double d'un nombre, il se comporte comme un double. La variable d'instance D'étudiant doit être écrite comme un POJO et suivre le modèle ci-dessus puisqu'elle ne s'enregistre pas.
{
"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) Enregistrer tous les sérialiseurs. La sortie est:
{
"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
}
}
exactement comme devrait.
une autre note IMPORTANTE: Si vous avez plusieurs sérialiseurs pour la même classe enregistrés avec le même Module, alors le Module sélectionnera le sérialiseur pour cette classe qui est la plus récente ajoutée à la liste. Cela ne devrait pas être utilisé - c'est confus et je ne suis pas sûr à quel point c'est cohérent
Moral: si vous voulez personnaliser la sérialisation des primitives dans votre objet, vous devez écrire votre propre sérialiseur pour l'objet. Vous ne pouvez pas compter sur la série POJO Jackson.
Jackson's JSON Views pourrait être un moyen plus simple de répondre à vos besoins, surtout si vous avez une certaine flexibilité dans votre format JSON.
si {"id":7, "itemNr":"TEST", "createdBy":{id:3}}
est une représentation acceptable alors ce sera très facile à réaliser avec très peu de code.
il vous suffit d'Annoter le champ Nom de L'utilisateur comme faisant partie d'une vue, et de spécifier une vue différente dans votre demande de sérialisation (les champs non annotés serait inclus par défaut)
par exemple: Définissez les vues:
public class Views {
public static class BasicView{}
public static class CompleteUserView{}
}
Annoter l'utilisateur:
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;
}
}
et serialise demandant une vue qui ne contient pas le champ que vous voulez masquer (les champs non annotés sont sérialisés par défaut):
objectMapper.getSerializationConfig().withView(Views.BasicView.class);
dans mon cas (Spring 3.2.4 et Jackson 2.3.1), configuration XML pour serializer personnalisé:
<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>
a été de manière inexpliquée réécrite par défaut par quelque chose.
cela a fonctionné pour moi:
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;
}
}
aucune configuration XML ( <mvc:message-converters>(...)</mvc:message-converters>
) n'est nécessaire dans ma solution.
j'ai écrit un exemple pour une personnalisation Timestamp.class
sérialisation/désérialisation, mais vous pouvez l'utiliser pour ce que vous voulez.
lors de la création de l'objet mapper faire quelque chose comme ceci:
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);
};
}
par exemple dans java ee
, vous pouvez l'initialiser avec ceci:
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;
}
}
où le serializer devrait être quelque chose comme ceci:
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;
}
}
et désérialiseur quelque chose comme ça:
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;
}
}
vous devez outrepasser la méthode handledType et tout fonctionnera
@Override
public Class<Item> handledType()
{
return Item.class;
}
si votre seule exigence dans votre sérialiseur personnalisé est de sauter la sérialisation du name
champ de User
, marquez-le comme transitoire . Jackson ne sérialisera ni ne désérialisera les champs transitoire .
[voir aussi: pourquoi Java a-t-il des champs transitoires? ]