我如何使用Jackson的自定义串行器?

我有两个我想用JSON序列化成JSON的Java类:

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

我想序列化一个项目到这个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) 

我如何使用Jackson的自定义串行器?


这是我如何与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不支持接口。

如前所述,@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也是比较安全的,因为它会有非必要方法的标准实现(比如实际的序列化调用)。

你可以把@JsonSerialize(using = CustomDateSerializer.class)放在要被序列化的对象的任何date字段上。

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

我也尝试过这样做,并且Jackson网页上的示例代码中存在一个错误,它无法在调用addSerializer方法时包含types(.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

希望这可以帮助!

使用@JsonValue:

 public class User { int id; String name; @JsonValue public int getId() { return id; } } 

@JsonValue只能用于方法,所以你必须添加getId方法。 您应该能够完全跳过您的自定义序列化程序。

这些是我在试图理解jackson系列化时注意到的行为模式。

1)假设有一个对象课堂和一个class级学生。 我已经把所有的事情都公之于众,最后放松了

 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)假设这些是我们用于将对象序列化成JSON的序列化器。 如果注册到对象映射器,writeObjectField使用对象自己的序列化器; 如果没有,那么它将它作为POJO序列化。 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)在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,使用DoubleSerialzer进行双精度,并使用常规string格式进行双精度。

4)注册DoubleSerializer和ClassroomSerializer,没有StudentSerializer。 我们期望输出是如此,如果我们写一个double作为一个对象,它的行为就像一个Double,如果我们写一个Double作为一个数字,它的行为就像一个double。 Student实例variables应该被写为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 } } 

完全如预期。

另一个重要的注意事项是:如果在同一模块中注册了同一个类的多个序列化程序,那么模块将select最近添加到列表中的那个类的序列化程序。 这不应该使用 – 这是混乱,我不知道这是多么一致

道德:如果你想自定义对象的原语序列化,你必须编写你自己的序列化程序的对象。 你不能依靠POJO Jackson系列化。

jackson的JSON视图可能是一个更简单的方式来实现您的要求,尤其是如果你有一些JSON格式的灵活性。

如果{"id":7, "itemNr":"TEST", "createdBy":{id:3}}是一个可以接受的表示forms,那么只需要很less的代码就可以很容易地实现。

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

例如:定义视图:

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

在我的情况下(Spring 3.2.4和Jackson 2.3.1),自定义序列化器的XMLconfiguration:

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

我的解决scheme中不需要XMLconfiguration( <mvc:message-converters>(...)</mvc:message-converters> )。

我写了一个自定义Timestamp.class序列化/反序列化的例子,但你可以用它来为你想要的。

创build对象映射器时,可以这样做:

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

你必须覆盖方法handlesType ,一切都会工作

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

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

[另请参阅: 为什么Java有瞬态字段? ]