简体   繁体   中英

Deserialize a Map from JAXB generated XML using Jackson

I need to deserialize some XMLs that are being generated using JAXB. Due to some compliance issue, I have to only use Jackson for XML parsing. I am getting below exception when trying to deserialize a class that has a Map

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
 at [Source: (StringReader); line: 40, column: 21] (through reference chain: FileConfig["otherConfigs"]->java.util.LinkedHashMap["entry"])

My code is as follows:

XML file:

. . .

    <fileConfig>
        <whetherNotify>false</whetherNotify>
        <url>....some location....</url>
        <includes>app.log</includes>
        <fileType>...some string...</fileType>
        <otherConfigs>
            <entry>
                <key>pathType</key>
                <value>1</value>
            </entry>
        </otherConfigs>
    </fileConfig>

. . .

FileConfig.java

    public class FileConfig implements Serializable {

    protected Boolean whetherNotify = false;
    protected String url;
    protected String includes;
    protected FileType fileType;
    private Map<String, String> otherConfigs = new HashMap<String, String>();

    ....getters and setters.....
}

Main.java

public class Main {
  .
  .
  .
  .
   private static <T> T unmarshallFromXML(String xml, Class<T> parseToClass) throws IOException {
        XmlMapper xmlMapper = new XmlMapper();
        xmlMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
        xmlMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        xmlMapper.enable(SerializationFeature.INDENT_OUTPUT);
        xmlMapper.setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
        xmlMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        xmlMapper.setDefaultUseWrapper(false);
        xmlMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        T parsedObject = xmlMapper.readValue(xml, parseToClass);
        return parsedObject;
    }
}

Please suggest a method to successfully parse that map using Jackson.

By default Map is serialized to XML in:

...
<key>value</key>
<key1>value1</key1>
...

format. There is not entry element. You have two options:

  1. Change model and instead of Map use List<Entry> type.
  2. Implement custom deserializer for a Map type.

New model

You need to create Entry class:

class Entry {
    private String key;
    private String value;

    // getters, setters, toString
}

and change property in FileConfig class to:

List<Entry> otherConfigs;

Custom map deserializer

See below example:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class XmlApp {

  public static void main(String[] args) throws Exception {
    File xmlFile = new File("./test.xml");


    XmlMapper xmlMapper = new XmlMapper();

    FileConfig fileConfig = xmlMapper.readValue(xmlFile, FileConfig.class);
    System.out.println(fileConfig);
  }
}

class MapEntryDeserializer extends JsonDeserializer<Map<String, String>> {

    @Override
    public Map<String, String> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        Map<String, String> map = new HashMap<>();

        JsonToken token;
        while ((token = p.nextToken()) != null) {
            if (token == JsonToken.FIELD_NAME) {
                if (p.getCurrentName().equals("entry")) {
                    p.nextToken();
                    JsonNode node = p.readValueAsTree();
                    map.put(node.get("key").asText(), node.get("value").asText());
                }
            }
        }
        return map;
    }
}

class FileConfig {

  protected Boolean whetherNotify = false;
  protected String url;
  protected String includes;

  @JsonDeserialize(using = MapEntryDeserializer.class)
  private Map<String, String> otherConfigs;

  // getters, setters, toString
}

Above code prints:

FileConfig{whetherNotify=false, url='....some location....', includes='app.log', otherConfigs={pathType1=2, pathType=1}}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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