web-dev-qa-db-fra.com

Comment utiliser un sérialiseur personnalisé avec Jackson?

J'ai deux classes Java que je souhaite sérialiser en JSON à l'aide de 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 souhaite sérialiser un élément sur ce JSON:

{"id":7, "itemNr":"TEST", "createdBy":3}

avec l’utilisateur sérialisé pour inclure uniquement le id. Je serai également en mesure de stériliser tous les objets utilisateur au format JSON comme:

{"id":3, "name": "Jonas", "email": "[email protected]"}

Donc, je suppose que je dois écrire un sérialiseur personnalisé pour Item et essayer 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érialise le JSON avec ce code de Jackson How-to: Serializers personnalisés :

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 utiliser un sérialiseur personnalisé avec Jackson?


Voici comment 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, car Gson ne prend pas en charge les interfaces.

100
Jonas

Comme mentionné, @JsonValue est un bon moyen. Mais si un sérialiseur personnalisé ne vous dérange pas, il n’est pas nécessaire d’en écrire un pour Item mais plutôt un pour User - dans l’affirmative, ce serait aussi simple que:

public void serialize(Item value, JsonGenerator jgen,
    SerializerProvider provider) throws IOException,
    JsonProcessingException {
  jgen.writeNumber(id);
}

Une autre possibilité consiste à implémenter JsonSerializable, auquel cas aucun enregistrement n'est nécessaire.

Quant à l'erreur; c'est bizarre - vous voulez probablement passer à une version ultérieure. Mais il est également plus sûr d’étendre org.codehaus.jackson.map.ser.SerializerBase car il disposera des implémentations standard de méthodes non essentielles (c’est-à-dire tout sauf l’appel de sérialisation actuel).

46
StaxMan

Vous pouvez placer @JsonSerialize(using = CustomDateSerializer.class) sur n'importe quel champ de 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);
    }

}
59
Moesio

J'ai essayé de faire cela aussi, et il y a une erreur dans l'exemple de code sur la page Web de Jackson qui n'inclut pas le type (.class) dans l'appel à la méthode addSerializer, qui devrait se lire comme suit:

simpleModule.addSerializer(Item.class, new ItemSerializer());

En d'autres termes, ce sont les lignes qui instancient le module simple et ajoutent le sérialiseur (avec la ligne précédente incorrecte commentée):

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 du bon exemple de code: http://wiki.fasterxml.com/JacksonFeatureModules

J'espère que cela t'aides!

31
pmhargis

Utilisez @JsonValue:

public class User {
    int id;
    String name;

    @JsonValue
    public int getId() {
        return id;
    }
}

@JsonValue ne fonctionne que sur les méthodes, vous devez donc ajouter la méthode getId. Vous devriez pouvoir ignorer votre sérialiseur personnalisé.

8
henrik_lundgren

Ce sont des comportements que j'ai remarqués en essayant de comprendre la sérialisation de Jackson.

1) Supposons qu’il existe un objet Classe et une classe Student. J'ai tout rendu public et final 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 en JSON. WriteObjectField utilise le propre sérialiseur de l'objet s'il est enregistré avec le mappeur d'objet; sinon, il le sérialise en tant que POJO. WriteNumberField accepte exclusivement les primitives en tant qu'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) Enregistrez uniquement un DoubleSerializer avec le modèle de sortie DecimalFormat ###,##0.000, dans SimpleModule et la sortie est la suivante:

{
  "double1" : 1234.5678,
  "Double1" : {
    "value" : "91,011.121"
  },
  "student1" : {
    "double2" : 1920.2122,
    "Double2" : {
      "value" : "2,324.253"
    }
  }
}

Vous pouvez constater que la sérialisation POJO fait la distinction entre double et double, à l'aide de DoubleSerialzer pour les doubles et d'un format de chaîne standard pour les doubles.

4) Enregistrez DoubleSerializer et ClassroomSerializer, sans StudentSerializer. Nous nous attendons à ce que la sortie soit telle que si nous écrivons un double sous forme d'objet, il se comporte comme un double et si nous écrivons un double sous forme de nombre, il se comporte comme un double. La variable d'instance Student doit être écrite en tant que POJO et suivre le modèle ci-dessus car 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) Enregistrez 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 prévu.

Autre remarque importante: si vous avez plusieurs sérialiseurs de la même classe enregistrés avec le même module, le module sélectionne alors le sérialiseur de cette classe ajouté à la liste. Cela ne devrait pas être utilisé - c'est déroutant et je ne suis pas sûr que cela soit cohérent

Morale: si vous souhaitez 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érialisation POJO Jackson.

7
laughing_man

Dans mon cas (Spring 3.2.4 et Jackson 2.3.1), configuration XML pour le sérialiseur 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>

était inexplicablement écrasé 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 requise dans ma solution.

5
user11153

Les vues JSON de Jackson pourrait être un moyen plus simple de répondre à vos besoins, en particulier si vous avez une certaine flexibilité dans votre format JSON.

Si {"id":7, "itemNr":"TEST", "createdBy":{id:3}} est une représentation acceptable, ce sera très facile à réaliser avec très peu de code.

Vous voudriez simplement annoter le champ de nom d'utilisateur en tant que partie d'une vue et spécifier une vue différente dans votre demande de sérialisation (les champs non annotés seraient 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 sérialisez en 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
Paul M

J'ai écrit un exemple pour une sérialisation/désérialisation personnalisée Timestamp.class, mais vous pouvez l'utiliser comme bon vous semble.

Lors de la création du mappeur d'objets, procédez comme suit:

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 sérialiseur devrait ressembler à 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 ceci:

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
madx

Si votre seul besoin dans votre sérialiseur personnalisé est de ne pas sérialiser le champ name de User, marquez-le comme transitoire . Jackson ne sérialisera ni ne désérialisera les champs transitoires .

[voir aussi: Pourquoi Java a-t-il des champs transitoires? ]

1
Mike G

Vous devez écraser la méthode handleType et tout fonctionnera

@Override
public Class<Item> handledType()
{
  return Item.class;
}
1
Sergey Kukurudzyak