Jackson 序列化:XML 和 JSON 的不同格式
Posted
技术标签:
【中文标题】Jackson 序列化:XML 和 JSON 的不同格式【英文标题】:Jackson Serialization: Different formats for XML and JSON 【发布时间】:2017-05-30 14:46:57 【问题描述】:我使用 Jackson 将我的应用程序模型序列化/反序列化为 JSON 和 XML(两者都需要)。
模型类:
@JacksonXmlRootElement
public class Data
@JsonProperty("attributes")
@JsonDeserialize(using = AttributesDeserializer.class)
@JsonSerialize(using = AttributesSerializer.class)
@JacksonXmlElementWrapper
private Map<Key, Map<String, Attribute>> attributes;
....
public class Key
private Integer id;
private String name;
....
public class Attribute
private Integer id;
private Integer value;
private String name;
我需要我的 JSON 看起来像这样:
"attributes": [
"key":
"id": 10,
"name": "key1"
,
"value":
"numeric":
"id": 1,
"value": 100,
"name": "numericAttribute"
,
"text":
"id": 2,
"value": 200,
"name": "textAttribute"
,
"key":
"id": 20,
"name": "key2"
,
"value":
"numeric":
"id": 1,
"value": 100,
"name": "numericAttribute"
,
"text":
"id": 2,
"value": 200,
"name": "textAttribute"
]
我的 XML 是这样的:
<Data>
<attributes>
<key>
<id>10</id>
<name>key1</name>
</key>
<value>
<numeric>
<id>1</id>
<value>100</value>
<name>numericAttribute</name>
</numeric>
<text>
<id>2</id>
<value>200</value>
<name>textAttribute</name>
</text>
</value>
<key>
<id>20</id>
<name>key2</name>
</key>
<value>
<numeric>
<id>1</id>
<value>100</value>
<name>numericAttribute</name>
</numeric>
<text>
<id>2</id>
<value>200</value>
<name>textAttribute</name>
</text>
</value>
</attributes>
</Data>
我正在使用自定义序列化程序获取所需的 JSON 和 XML:
public class AttributesSerializer extends JsonSerializer<Map<Key, Map<String, Attribute>>>
@Override
public void serialize(Map<Key, Map<String, Attribute>> map, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException
jsonGenerator.writeStartArray();
for (Map.Entry<Key, Map<String, Attribute>> entry : map.entrySet())
jsonGenerator.writeStartObject();
jsonGenerator.writeObjectField("key", entry.getKey());
jsonGenerator.writeObjectFieldStart("value");
for (Map.Entry<String, Attribute> attributesEntry : entry.getValue().entrySet())
jsonGenerator.writeObjectField(attributesEntry.getKey(), attributesEntry.getValue());
jsonGenerator.writeEndObject();
jsonGenerator.writeEndObject();
jsonGenerator.writeEndArray();
使用自定义反序列化器的 JSON 反序列化效果很好:
public class AttributesDeserializer extends JsonDeserializer<Map<Key, Map<String, Attribute>>>
@Override
public Map<Key, Map<String, Attribute>> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
if (node.size() == 0)
return null;
ObjectMapper om = new ObjectMapper();
Map<Key, Map<String, Attribute>> attributes = new HashMap<>();
node.forEach(jsonNode ->
Map<String, Attribute> attributesMap = new HashMap<>();
JsonNode keyNode = jsonNode.get("key");
Key key = om.convertValue(keyNode, Key.class);
JsonNode valueNode = jsonNode.get("value");
Iterator<Map.Entry<String, JsonNode>> attributesIterator = valueNode.fields();
while(attributesIterator.hasNext())
Map.Entry<String, JsonNode> field = attributesIterator.next();
Attribute attribute = om.convertValue(field.getValue(), Attribute.class);
attributesMap.put(field.getKey(), attribute);
attributes.put(key, attributesMap);
);
return attributes;
虽然对于 JSON 来说一切正常,但对于 XML,应用程序在反序列化时崩溃:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: ro.alexsvecencu.jackson.Data["attributes"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:388)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:348)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1599)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:278)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2740)
at ro.alexsvecencu.jackson.Main.main(Main.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.NullPointerException
at ro.alexsvecencu.jackson.AttributesDeserializer.lambda$deserialize$0(AttributesDeserializer.java:29)
at ro.alexsvecencu.jackson.AttributesDeserializer$$Lambda$1/1709366259.accept(Unknown Source)
at java.lang.Iterable.forEach(Iterable.java:75)
at ro.alexsvecencu.jackson.AttributesDeserializer.deserialize(AttributesDeserializer.java:24)
at ro.alexsvecencu.jackson.AttributesDeserializer.deserialize(AttributesDeserializer.java:15)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:101)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:276)
... 9 more
发生的事情是我的自定义反序列化器为 XML 崩溃,因为显然它没有将所有属性解释为“数组”,当我遍历 jsonNode 的子节点时,它会遍历键/值。另外,通过调试,我注意到反序列化器只是为 XML 中属性的 LAST 标记调用。
有什么方法可以告诉 Jackson 使用针对 XML 和 JSON 不同的特定自定义反序列化器/序列化器?这是我认为可以解决的一种方法。
我的 XML 格式可能有点不同(我并没有真正限制它的格式,但 JSON 必须保持这种格式)。有了这种灵活性,您有没有其他方法可以解决我的问题?我可以对 XML 使用不同的东西,比如 JAXB,但我几乎只能对两者都使用 Jackson。
【问题讨论】:
【参考方案1】:我有一个部分解决方案给你。使用Jackson mixin feature,可以为 XML 和 JSON 提供不同的自定义反序列化器/序列化器
首先,您创建另一个 POJO 类,该类的属性与Data
类的属性同名,自定义反序列化器/序列化器具有不同的注释
@JacksonXmlRootElement
public static class XmlData
@JsonProperty("attributes")
@JsonDeserialize(using = XmlAttributesDeserializer.class) // specify different serializer
@JsonSerialize(using = XmlAttributesSerializer.class) // specify different deserializer
@JacksonXmlElementWrapper
public Map<Key, Map<String, Attribute>> attributes;
接下来,您创建一个Jackson Module,将Data
类与mixin XmlData
类相关联,
@SuppressWarnings("serial")
public static class XmlModule extends SimpleModule
public XmlModule()
super("XmlModule");
@Override
public void setupModule(SetupContext context)
context.setMixInAnnotations(Data.class, XmlData.class);
这是一个测试方法,展示了如何将模块注册到映射器并动态序列化为不同的格式:
public static void main(String[] args)
Attribute a1 = new Attribute();
a1.id = 1;
a1.value = 100;
a1.name = "numericAttribute";
Attribute a2 = new Attribute();
a2.id = 2;
a2.value = 200;
a2.name = "textAttribute";
Map<String, Attribute> atts = new HashMap<>();
atts.put("numeric", a1);
atts.put("text", a2);
Key k1 = new Key();
k1.id = 10;
k1.name = "key1";
Key k2 = new Key();
k2.id = 20;
k2.name = "key2";
Data data = new Data();
data.attributes = new HashMap<>();
data.attributes.put(k1, atts);
data.attributes.put(k2, atts);
ObjectMapper mapper;
if ("xml".equals(args[0]))
mapper = new XmlMapper();
mapper.registerModule(new XmlModule());
else
mapper = new ObjectMapper();
try
mapper.writeValue(System.out, data);
catch (Exception e)
e.printStackTrace();
【讨论】:
【参考方案2】:除了sharonbn 提供的解决方案之外,我发现如果您可以将字段封装到不同的类中,则可以通过 Json (ObjectMapper) 或 Xml (XmlMapper) 的模块注册不同的序列化程序。
这是一个实际的例子:
public class CustomSerializers
public static class PojoField
PojoField()
value = "PojoField";
public String value;
@JacksonXmlRootElement
public static class Pojo
Pojo()
field = new PojoField();
@JsonProperty("field")
public PojoField field;
public static class PojoFieldJSonSerializer extends JsonSerializer<PojoField>
@Override
public void serialize(PojoField pojoField, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException
jsonGenerator.writeObject(pojoField.value + " in JSON");
public static class PojoFieldXmlSerializer extends JsonSerializer<PojoField>
@Override
public void serialize(PojoField pojoField, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException
jsonGenerator.writeObject(pojoField.value + " in XMl");
public static void main(String []args) throws IOException
Pojo pojo = new Pojo();
SimpleModule objectMapperModule = new SimpleModule();
objectMapperModule.addSerializer(PojoField.class, new PojoFieldJSonSerializer());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(objectMapperModule);
objectMapper.writeValue(new File("pojo.json"), pojo);
SimpleModule xmlMapperModule = new SimpleModule();
xmlMapperModule.addSerializer(PojoField.class, new PojoFieldXmlSerializer());
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.registerModule(xmlMapperModule);
xmlMapper.writeValue(new File("pojo.xml"), pojo);
JSON 的输出将是:
"field": "PojoField in JSON"
XML 的输出:
<Pojo>
<field>PojoField in XMl</field>
</Pojo>
【讨论】:
【参考方案3】:对于 Json 和 Xml 使用唯一的 Serializer/Derializer 并在其中检查 JsonGenerator 的类型(json 或 xml)并应用与特定格式相关的逻辑是否是解决方案? 就这样
public class AttributeSerializer extends JsonSerializer<Map<Key, Map<String, Attribute>>>
@Override
public void serialize(Map<Key, Map<String, Attribute>> value, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException
if (gen instanceof ToXmlGenerator)
//apply logic to format xml structure
else
//apply logic to format json or else
我知道使用instaceOf
不是一个好的架构,但是这样我们可以避免 dto 对 xml 的重复。
【讨论】:
以上是关于Jackson 序列化:XML 和 JSON 的不同格式的主要内容,如果未能解决你的问题,请参考以下文章
Jackson:为 Map 数据结构注册自定义 XML 序列化程序