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

Posted

技术标签:

【中文标题】如何在 Jackson 中使用自定义序列化程序?【英文标题】:How do I use a custom Serializer with Jackson? 【发布时间】:2011-11-01 23:06:02 【问题描述】:

我有两个 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;
    

我想将一个 Item 序列化为这个 JSON:

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

用户序列化为仅包含id。我还可以将所有用户对象序列化为 JSON,例如:

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

所以我想我需要为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();



我使用来自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();

但我收到此错误:

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)


这就是我对 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);

但我现在需要使用 Jackson,因为 Gson 不支持接口。

【问题讨论】:

你是如何/在哪里让 Jackson 使用你的自定义序列化器为 Item 的?我遇到了一个问题,我的控制器方法返回标准序列化对象TypeA,但对于另一个特定的控制器方法,我想以不同的方式对其进行序列化。那会是什么样子? 我写了一篇关于How to Write a Custom Serializer with Jackson 的帖子,可能对某些人有帮助。 【参考方案1】:

您的问题是 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;
   

因此,您会收到 handledType() 未定义的显式错误

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

希望它可以帮助某人。感谢您阅读我的回答。

【讨论】:

【参考方案2】:

我也试过这样做,Jackson 网页上的示例代码有一个错误,未能在对 addSerializer() 方法的调用中包含类型 (.class),它应该是这样的:

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

换句话说,这些是实例化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);

仅供参考:以下是正确示例代码的参考:http://wiki.fasterxml.com/JacksonFeatureModules

【讨论】:

【参考方案3】:

您可以将@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);
    


【讨论】:

值得注意:注释集合时使用@JsonSerialize(contentUsing= ...)(例如@JsonSerialize(contentUsing= CustomDateSerializer.class) List&lt;Date&gt; dates【参考方案4】:

我为自定义Timestamp.class 序列化/反序列化编写了一个示例,但您可以随意使用它。

创建对象映射器时,请执行以下操作:

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

例如,在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;
    

序列化器应该是这样的:

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;
    

和反序列化器是这样的:

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;
    

【讨论】:

【参考方案5】:

这些是我在尝试理解 Jackson 序列化时注意到的行为模式。

1) 假设有一个对象 Clas-s-room 和一个班级 Student。为了方便起见,我已将所有内容公开并最终确定。

public class Clas-s-room 
    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) 假设这些是我们用于将对象序列化为 JSON 的序列化程序。如果 writeObjectField 使用对象映射器注册,则使用对象自己的序列化器;如果不是,则将其序列化为 POJO。 writeNumberField 只接受原语作为参数。

public class Clas-s-roomSerializer extends StdSerializer<Clas-s-room> 
    public Clas-s-roomSerializer(Class<Clas-s-room> t) 
        super(t);
    

    @Override
    public void serialize(Clas-s-room 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) 在 SimpleModule 中仅注册一个具有 DecimalFormat 输出模式###,##0.000 的 DoubleSerializer,输出为:


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

您可以看到 POJO 序列化区分 double 和 Double,对 Doubles 使用 DoubleSerialzer,对 doubles 使用常规 String 格式。

4) 注册 DoubleSerializer 和 Clas-s-roomSerializer,无需 StudentSerializer。我们期望输出是这样的:如果我们将 double 写为对象,它的行为类似于 Double,如果我们将 Double 写为数字,它的行为类似于 double。 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) 注册所有序列化器。输出是:


  "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
  

完全符合预期。

另一个重要注意事项:如果您为同一个类注册了同一个模块的多个序列化程序,那么该模块将为该类选择最近添加到列表中的序列化程序。不应该使用它 - 它令人困惑,我不确定这是多么一致

道德:如果你想在你的对象中自定义原语的序列化,你必须为对象编写你自己的序列化器。你不能依赖 POJO Jackson 序列化。

【讨论】:

你如何注册 Clas-s-roomSerializer 来处理例如课堂发生了什么?【参考方案6】:

就我而言(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>

被某些东西以无法解释的方式覆盖回默认值。

这对我有用:

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;
    

我的解决方案中不需要 XML 配置 (&lt;mvc:message-converters&gt;(...)&lt;/mvc:message-converters&gt;)。

【讨论】:

【参考方案7】:

你必须重写方法 handledType 一切都会正常工作

@Override
public Class<Item> handledType()

  return Item.class;

【讨论】:

【参考方案8】:

Jackson's JSON Views 可能是满足您要求的一种更简单的方法,尤其是当您的 JSON 格式具有一定的灵活性时。

如果"id":7, "itemNr":"TEST", "createdBy":id:3 是可接受的表示,那么这将很容易用很少的代码实现。

您只需将 User 的 name 字段注释为视图的一部分,并在您的序列化请求中指定不同的视图(默认情况下将包含未注释的字段)

例如: 定义视图:

public class Views 
    public static class BasicView
    public static class CompleteUserView

注释用户:

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;
    

并序列化请求不包含您要隐藏的字段的视图(默认情况下对未注释的字段进行序列化):

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

【讨论】:

我发现 Jackson JSON 视图很难使用,并且无法为这个问题找到一个好的解决方案。 Jonas - 我添加了一个示例。我发现视图是一个非常好的解决方案,可以用不同的方式序列化同一个对象。 感谢您提供了一个很好的例子。这是迄今为止最好的解决方案。但是有没有办法将createdBy 作为一个值而不是一个对象? setSerializationView() 似乎已被弃用,所以我改用mapper.viewWriter(JacksonViews.ItemView.class).writeValue(writer, myItem); 我怀疑它使用 jsonviews。我在发现视图之前使用的一个快速而肮脏的解决方案就是将我感兴趣的属性复制到地图中,然后序列化地图。【参考方案9】:

如前所述,@JsonValue 是一个好方法。但是,如果您不介意自定义序列化程序,则无需为 Item 编写一个,而为 User 编写一个 - 如果是这样,它会很简单:

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

还有一种可能是实现JsonSerializable,在这种情况下不需要注册。

关于错误;这很奇怪——您可能想升级到更高版本。但是扩展org.codehaus.jackson.map.ser.SerializerBase 也更安全,因为它将具有非必要方法的标准实现(即除实际序列化调用之外的所有方法)。

【讨论】:

这样我得到了同样的错误:Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.JsonTest$UserSerilizer 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.&lt;init&gt;(JsonTest.java:27) at com.exampple.JsonTest.main(JsonTest.java:102) 我使用的是 Jacskson 的最新稳定版本,1.8.5。 谢谢。我去看看……啊!它实际上很简单(虽然错误信息不好)——你只需要用不同的方法注册序列化器,指定序列化器的类:如果没有,它必须从handledType()返回类。因此,使用以 JavaType 或 Class 作为参数的“addSerializer”,它应该可以工作。 如果没有运行怎么办?【参考方案10】:

使用@JsonValue:

public class User 
    int id;
    String name;

    @JsonValue
    public int getId() 
        return id;
    

@JsonValue 仅适用于方法,因此您必须添加 getId 方法。 您应该可以完全跳过自定义序列化程序。

【讨论】:

我认为这将影响序列化用户的所有尝试,从而难以通过 JSON 公开用户名。 我不能使用这个解决方案,因为我还需要能够序列化具有所有字段的所有用户对象。这个解决方案将打破这种序列化,因为只包含 id 字段。有没有办法像为 Gson 一样为 Jackson 创建自定义序列化器? 您能否评论一下为什么 JSON 视图(在我的回答中)不符合您的需求? @user:这可能是一个很好的解决方案,我正在阅读并尝试。 请注意,您可以使用 @JsonSerialize(using=MySerializer.class) 来指示您的属性(字段或 getter)的特定序列化,因此它仅用于成员属性而不是所有实例类型。【参考方案11】:

如果您在自定义序列化程序中的唯一要求是跳过序列化Username 字段,请将其标记为transient。 Jackson 不会序列化或反序列化 transient 字段。

[参见:Why does Java have transient fields?]

【讨论】:

我在哪里标记它?在User 类中?但我也会序列化所有用户对象。例如。首先只序列化所有items(只有userId作为对用户对象的引用)然后序列化所有users。在这种情况下,我无法标记User-class 中的字段。 鉴于此新信息,此方法不适合您。看起来 Jackson 正在寻找自定义序列化程序的更多信息(handledType() 方法需要覆盖?) 是的,但是我链接到的文档中没有关于handledType() 方法的内容,并且当Eclipse 生成实现没有handledType() 的方法时,我很困惑。 我不确定,因为您链接的 wiki 没有引用它,但在 1.5.1 版本中有一个handledType() 并且异常似乎在抱怨该方法丢失或无效(基本类从方法返回 null)。 jackson.codehaus.org/1.5.1/javadoc/org/codehaus/jackson/map/…

以上是关于如何在 Jackson 中使用自定义序列化程序?的主要内容,如果未能解决你的问题,请参考以下文章

Jackson:如何在不修改 POJO 的情况下向 JSON 添加自定义属性

Spring MVC for Rest 服务如何在 Jackson 反序列化错误时自定义错误“消息”?

在 Jackson / Spring Boot 中测试自定义 Json Deserializer

在其他反序列化器中调用自定义 Jackson 反序列化器

Jackson:为 Map 数据结构注册自定义 XML 序列化程序

JSON Jackson - 使用自定义序列化程序序列化多态类时的异常