【问题标题】:How to deserealize multiple nested elements in Jackson?如何反序列化杰克逊中的多个嵌套元素?
【发布时间】:2019-12-06 16:03:30
【问题描述】:

我需要构建一个解析器来将XML 文件解析为Java 对象。 我使用Jackson 来执行此操作,并按照THIS 教程中提供的步骤进行操作。

本教程中有一节“在 XML 中操作嵌套元素和列表”。我跟着它,但不幸的是,我无法获得所有所需元素的所需输出 - 我想输出我所有作者的第一个和最后一个。我只在XML-file 中为我的最后一位作者得到它,如下所示:

[{nameList={person={first=Karl, last=S}}}]

我的XML 文件如下所示。

<sources>
<Doi>123456789</Doi>
<Title>Title</Title>
<author>
    <editor>
        <nameList>
            <person>
                <first>Peter</first>
                <last>Parker</last>
            </person>
        </nameList>
    </editor>
</author>
<Source>
    <SourceType>Book</SourceType>
    <ShortTitle>Book Title</ShortTitle>
    <Author>
        <Editor>
            <NameList />
        </Editor>
    </Author>
</Source>
<author>
    <bookAuthor>
        <nameList>
            <person>
                <first>Karl</first>
                <last>S</last>
            </person>
        </nameList>
    </bookAuthor>
</author>
<Source>
    <SourceType>Journal</SourceType>
    <ShortTitle>ABC Journal</ShortTitle>
</Source>
</sources>

我怎样才能取消整个 XML 文件的实现?

我的代码如下所示: MyClass.java

private static void jacksonXmlFileToObject() throws IOException {

    System.out.println("jacksonXmlFileToObject");

    InputStream xmlFile = Publication.class.getClassLoader().getResourceAsStream("test.xml");
    ObjectMapper mapper = new XmlMapper();

    // Configure
    mapper
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    try {

        Sources deserializedData = mapper.readValue(xmlFile, Sources.class);

        System.out.println(deserializedData);

    } catch (IOException e) {
        e.printStackTrace();
    }
}

Sources.java

@EqualsAndHashCode
@JacksonXmlRootElement(localName = "sources") public class Sources {
@JacksonXmlElementWrapper(localName = "author")
@Getter
@Setter
private Object[] author;

@Override
public String toString() {
    return Arrays.toString(author);
}

public Sources() {
}
}

如果能得到一些帮助,我会很高兴的。

谢谢!

【问题讨论】:

  • 看起来您有&lt;author&gt;&lt;Source&gt; 混合节点。如果您需要作者,这意味着您需要跳过所有 &lt;Source&gt; 节点,对吗?为什么你的输出不包含Peter Parker
  • @MichałZiober 感谢您的回复。是的,我有混合节点,但我只想拥有作者,分别为firstlast。而且我不确定为什么我的输出不包含Peter Parker

标签: java xml parsing jackson pojo


【解决方案1】:

当相同的元素没有相互跟随时,JacksonXmlElementWrapper 似乎不起作用。常规XML 应该包含一个接一个列出的相同节点。当其他节点启动时,意味着前一个节点部分已完成。为了处理您的情况,我们需要编写自定义反序列化器:手动读取所有作者并跳过其余节点。示例代码可能如下所示:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonPointer;
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.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class XmlMapperApp {

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

        XmlMapper mapper = new XmlMapper();

        System.out.println(mapper.readValue(xmlFile, Sources.class));
    }
}

class SourcesJsonDeserializer extends JsonDeserializer<Sources> {

    private final JsonPointer EDITOR = JsonPointer.compile("/editor/nameList/person");
    private final JsonPointer BOOK_AUTHOR = JsonPointer.compile("/bookAuthor/nameList/person");

    @Override
    public Sources deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        List<JsonNode> authors = new ArrayList<>();
        JsonToken token;
        while ((token = p.currentToken()) != null) {
            if (token == JsonToken.FIELD_NAME) {
                if ("author".equals(p.getText())) {
                    authors.add(getPersonObject(p));
                }
            }
            p.nextToken();
        }

        Sources sources = new Sources();
        sources.setAuthors(authors);

        return sources;
    }

    private JsonNode getPersonObject(JsonParser p) throws IOException {
        // read start object
        p.nextToken();

        // read the whole object as node
        ObjectNode author = p.readValueAsTree();

        // try to evaluate /editor/* path
        JsonNode pair = author.at(EDITOR);
        if (pair.isMissingNode()) {
            // must be bookAuthor
            pair = author.at(BOOK_AUTHOR);
        }

        return pair;
    }
}

@JsonDeserialize(using = SourcesJsonDeserializer.class)
class Sources {

    private List<JsonNode> authors;

    public List<JsonNode> getAuthors() {
        return authors;
    }

    public void setAuthors(List<JsonNode> authors) {
        this.authors = authors;
    }

    @Override
    public String toString() {
        return authors + "";
    }
}

上面的代码打印:

[{"first":"Peter","last":"Parker"}, {"first":"Karl","last":"S"}]

【讨论】:

  • 谢谢你的例子。如何确保可以取消实现标签中的多个标签?我尝试了不同的方法here,但在这里也遇到了同样的挑战。
【解决方案2】:

使用JsonMerge 注释。

我自己最近也遇到了类似的问题,发现@JsonMerge这个注解解决了这个问题。

我稍微简化了 XML:

<sources>
    <author>
        <name>Jack</name>
    </author>
    <source>
        <type>Book</type>
    </source>
    <author>
        <name>Jill</name>
    </author>
    <source>
        <type>Journal</type>
    </source>
</sources>

使用 AuthorSource

class Author {
    String name;
}
class Source {
    String type;
}

Sources 类如下所示:

class Sources {

    // We prevent each <author> tag to be wrapped in an <authors> container tag
    @JacksonXmlElementWrapper(useWrapping = false)

    // Each element is <author> and not <authors> (and we named our field 'authors')
    @JacksonXmlProperty(localName = "author")

    // This is the property which solves your problem. It causes non-subsequent elements with the
    // same name to be merged into the existing list
    @JsonMerge
    private List<Author> authors;

    @JacksonXmlElementWrapper(useWrapping = false)
    @JacksonXmlProperty(localName = "source")
    @JsonMerge
    private List<Source> sources;
}

【讨论】:

    【解决方案3】:

    嗯,我自己最近也遇到了类似的问题,发现@JsonMerge这个注解解决了这个问题。

    我稍微简化了 XML:

    <sources>
        <author>
            <name>Jack</name>
        </author>
        <source>
            <type>Book</type>
        </source>
        <author>
            <name>Jill</name>
        </author>
        <source>
            <type>Journal</type>
        </source>
    </sources>
    

    使用 AuthorSource

    class Author {
        String name;
    }
    class Source {
        String type;
    }
    

    Sources 类如下所示:

    class Sources {
    
        // We prevent each <author> tag to be wrapped in an <authors> container tag
        @JacksonXmlElementWrapper(useWrapping = false)
    
        // Each element is <author> and not <authors> (and we named our field 'authors')
        @JacksonXmlProperty(localName = "author")
    
        // This is the property which solves your problem. It causes non-subsequent elements
        // with the same name to be merged into the existing list
        @JsonMerge
        private List<Author> authors;
    
        @JacksonXmlElementWrapper(useWrapping = false)
        @JacksonXmlProperty(localName = "source")
        @JsonMerge
        private List<Source> sources;
    }
    

    【讨论】:

      猜你喜欢
      • 2012-12-22
      • 2019-11-15
      • 2021-10-02
      • 1970-01-01
      • 2013-11-04
      • 1970-01-01
      • 1970-01-01
      • 2016-01-14
      • 2016-12-07
      相关资源
      最近更新 更多