简体   繁体   English

如何在 Jackson 中使用自定义序列化程序?

[英]How do I use a custom Serializer with Jackson?

I have two Java classes that I want to serialize to JSON using Jackson:我有两个 Java 类要使用 Jackson 序列化为 JSON:

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

I want to serialize an Item to this JSON:我想将一个 Item 序列化为这个 JSON:

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

with User serialized to only include the id .用户序列化为仅包含id I will also be able to serilize all user objects to JSON like:我还可以将所有用户对象序列化为 JSON,例如:

{"id":3, "name": "Jonas", "email": "jonas@example.com"}

So I guess that I need to write a custom serializer for Item and tried with this:所以我想我需要为Item编写一个自定义序列化程序并尝试这样做:

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

}

I serialize the JSON with this code from Jackson How-to: Custom Serializers :我使用Jackson How-to: Custom Serializers 中的代码序列化 JSON:

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

But I get this 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)

How can I use a custom Serializer with Jackson?如何在 Jackson 中使用自定义序列化程序?


This is how I would do it with Gson:这就是我对 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);

But I need to do it with Jackson now, since Gson doesn't have support for interfaces.但我现在需要和 Jackson 一起做,因为 Gson 不支持接口。

You can put @JsonSerialize(using = CustomDateSerializer.class) over any date field of object to be serialized.您可以将@JsonSerialize(using = CustomDateSerializer.class)放在要序列化的对象的任何日期字段上。

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

}

As mentioned, @JsonValue is a good way.如前所述,@JsonValue 是一个好方法。 But if you don't mind a custom serializer, there's no need to write one for Item but rather one for User -- if so, it'd be as simple as:但是,如果您不介意自定义序列化程序,则无需为 Item 编写一个,而是为 User 编写一个——如果是这样,那就很简单了:

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

Yet another possibility is to implement JsonSerializable , in which case no registration is needed.另一种可能性是实现JsonSerializable ,在这种情况下不需要注册。

As to error;至于错误; that is weird -- you probably want to upgrade to a later version.这很奇怪——您可能想升级到更高版本。 But it is also safer to extend org.codehaus.jackson.map.ser.SerializerBase as it will have standard implementations of non-essential methods (ie everything but actual serialization call).但扩展org.codehaus.jackson.map.ser.SerializerBase也更安全,因为它将具有非必要方法的标准实现(即除实际序列化调用之外的所有内容)。

I tried doing this too, and there is a mistake in the example code on the Jackson web page that fails to include the type ( .class ) in the call to addSerializer() method, which should read like this:我也尝试过这样做,并且 Jackson 网页上的示例代码中有一个错误,即未能在调用addSerializer()方法中包含类型 ( .class ),它应该是这样的:

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

In other words, these are the lines that instantiate the simpleModule and add the serializer (with the prior incorrect line commented out):换句话说,这些是实例化simpleModule并添加序列化程序的行(注释掉先前不正确的行):

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: Here is the reference for the correct example code: http://wiki.fasterxml.com/JacksonFeatureModules仅供参考:这是正确示例代码的参考: http : //wiki.fasterxml.com/JacksonFeatureModules

I wrote an example for a custom Timestamp.class serialization/deserialization, but you could use it for what ever you want.我为自定义Timestamp.class序列化/反序列化编写了一个示例,但您可以根据需要使用它。

When creating the object mapper do something like this:创建对象映射器时,请执行以下操作:

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

for example in java ee you could initialize it with this:例如在java ee您可以使用以下方法对其进行初始化:

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

where the serializer should be something like this:序列化程序应该是这样的:

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

and deserializer something like this:和解串器是这样的:

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

Use @JsonValue:使用@JsonValue:

public class User {
    int id;
    String name;

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

@JsonValue only works on methods so you must add the getId method. @JsonValue 仅适用于方法,因此您必须添加 getId 方法。 You should be able to skip your custom serializer altogether.您应该能够完全跳过您的自定义序列化程序。

These are behavior patterns I have noticed while trying to understand Jackson serialization.这些是我在尝试理解 Jackson 序列化时注意到的行为模式。

1) Assume there is an object Classroom and a class Student. 1) 假设有一个对象 Classroom 和一个类 Student。 I've made everything public and final for ease.为了方便起见,我已将所有内容公开并最终确定。

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) Assume that these are the serializers we use for serializing the objects into JSON. 2) 假设这些是我们用于将对象序列化为 JSON 的序列化程序。 The writeObjectField uses the object's own serializer if it is registered with the object mapper;如果 writeObjectField 已向对象映射器注册,则它使用对象自己的序列化程序; if not, then it serializes it as a POJO.如果没有,则将其序列化为 POJO。 The writeNumberField exclusively only accepts primitives as arguments. writeNumberField 只接受原语作为参数。

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) Register only a DoubleSerializer with DecimalFormat output pattern ###,##0.000 , in SimpleModule and the output is: 3) 在 SimpleModule 中仅注册一个带有 DecimalFormat 输出模式###,##0.000的 DoubleSerializer ,输出为:

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

You can see that the POJO serialization differentiates between double and Double, using the DoubleSerialzer for Doubles and using a regular String format for doubles.您可以看到 POJO 序列化区分 double 和 Double,对 Double 使用 DoubleSerialzer,对 double 使用常规 String 格式。

4) Register DoubleSerializer and ClassroomSerializer, without the StudentSerializer. 4) 注册 DoubleSerializer 和 ClassroomSerializer,没有 StudentSerializer。 We expect that the output is such that if we write a double as an object, it behaves like a Double, and if we write a Double as a number, it behaves like a double.我们期望输出是这样的,如果我们将 double 写为对象,它的行为就像一个 Double,如果我们将一个 Double 写成一个数字,它的行为就像一个 double。 The Student instance variable should be written as a POJO and follow the pattern above since it does not register. Student 实例变量应该写成一个 POJO 并遵循上面的模式,因为它没有注册。

{
  "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) Register all serializers. 5) 注册所有序列化程序。 The output is:输出是:

{
  "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
  }
}

exactly as expected.完全符合预期。

Another important note: If you have multiple serializers for the same class registered with the same Module, then the Module will select the serializer for that class that is most recently added to the list.另一个重要的注意事项:如果您在同一个模块中注册了同一个类的多个序列化程序,那么模块将为该类选择最近添加到列表中的序列化程序。 This should not be used - it's confusing and I am not sure how consistent this is这不应该被使用 - 这很令人困惑,我不确定这有多一致

Moral: if you want to customize serialization of primitives in your object, you must write your own serializer for the object.道德:如果要自定义对象中原语的序列化,则必须为对象编写自己的序列化程序。 You cannot rely on the POJO Jackson serialization.您不能依赖 POJO Jackson 序列化。

Jackson's JSON Views might be a simpler way of achieving your requirements, especially if you have some flexibility in your JSON format. Jackson 的 JSON 视图可能是满足您要求的一种更简单的方法,尤其是当您的 JSON 格式具有一定的灵活性时。

If {"id":7, "itemNr":"TEST", "createdBy":{id:3}} is an acceptable representation then this will be very easy to achieve with very little code.如果{"id":7, "itemNr":"TEST", "createdBy":{id:3}}是一个可接受的表示,那么这将很容易用很少的代码实现。

You would just annotate the name field of User as being part of a view, and specify a different view in your serialisation request (the un-annotated fields would be included by default)您只需将 User 的 name 字段注释为视图的一部分,并在序列化请求中指定不同的视图(默认情况下将包含未注释的字段)

For example: Define the views:例如: 定义视图:

public class Views {
    public static class BasicView{}
    public static class CompleteUserView{}
}

Annotate the User:注释用户:

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

And serialise requesting a view which doesn't contain the field you want to hide (non-annotated fields are serialised by default):并序列化请求不包含要隐藏的字段的视图(默认情况下将序列化未注释的字段):

objectMapper.getSerializationConfig().withView(Views.BasicView.class);

In my case (Spring 3.2.4 and Jackson 2.3.1), XML configuration for custom serializer:就我而言(Spring 3.2.4 和 Jackson 2.3.1),自定义序列化程序的 XML 配置:

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

was in unexplained way overwritten back to default by something.以无法解释的方式被某些东西覆盖回默认值。

This worked for me:这对我有用:

CustomObject.java自定义对象.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自定义对象序列化器.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 XML configuration ( <mvc:message-converters>(...)</mvc:message-converters> ) is needed in my solution.我的解决方案不需要 XML 配置( <mvc:message-converters>(...)</mvc:message-converters> )。

You have to override method handledType and everything will work您必须覆盖方法handledType ,一切都会起作用

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

The problem in your case is the ItemSerializer is missing the method handledType() which needs to be overridden from JsonSerializer您的情况的问题是 ItemSerializer 缺少需要从 JsonSerializer 覆盖的方法handledType()

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

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

Hence you are getting the explicit error that handledType() is not defined因此,您收到了未定义 handleType()的显式错误

Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType() 

Hope it helps someone.希望它可以帮助某人。 Thanks for reading my answer.感谢您阅读我的回答。

If your only requirement in your custom serializer is to skip serializing the name field of User , mark it as transient .如果您在自定义序列化程序中的唯一要求是跳过序列化Username字段,请将其标记为瞬态 Jackson will not serialize or deserialize transient fields. Jackson 不会序列化或反序列化瞬态字段。

[ see also: Why does Java have transient fields? [另见: 为什么 Java 有瞬态字段? ] ]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM