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.

87
demandé sur jeffreyveon 2011-08-23 17:19:18

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

38
répondu StaxMan 2011-08-23 22:39:47

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

}
54
répondu Moesio 2017-01-31 17:52:30

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!

30
répondu pmhargis 2013-12-11 21:30:40

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.

7
répondu henrik_lundgren 2011-08-23 14:58:40

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.

7
répondu laughing_man 2014-03-01 00:42:51

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);
5
répondu Paul M 2013-06-05 13:19:19

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.

4
répondu user11153 2014-02-12 11:08:46

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;
    }
}
4
répondu madx 2016-06-13 07:14:55

vous devez outrepasser la méthode handledType et tout fonctionnera

@Override
public Class<Item> handledType()
{
  return Item.class;
}
1
répondu Sergey Kukurudzyak 2013-06-18 11:30:00

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? ]

0
répondu Mike G 2017-05-23 12:18:19