【问题标题】:Jackson Serialization: Different formats for XML and JSONJackson 序列化:XML 和 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。

【问题讨论】:

    标签: java json xml serialization 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 的重复。

        【讨论】:

          猜你喜欢
          • 2015-06-06
          • 2014-11-17
          • 2021-12-10
          • 1970-01-01
          • 1970-01-01
          • 2016-06-14
          • 1970-01-01
          • 1970-01-01
          • 2011-08-10
          相关资源
          最近更新 更多