JacksonXmlMapper使用 Package 器条目元素对Java Map进行XML序列化

ggazkfy8  于 7个月前  发布在  Java
关注(0)|答案(1)|浏览(81)

这个问题是关于JacksonXMLMap序列化和非序列化的。默认情况下,Jackson希望Map的键是XML元素本身。

<MyMap>
    <key1>value1</key1>
    <key2>value2</key2>
</MyMap>

但这使得不可能有无效XML元素名称的键。
在Jackson中,是否有一种方法可以通过 Package 条目XML元素(如下所示)将XML封装起来?

<MyMap>
    <MyEntry>
        <Key>key1</Key>
        <Value>value1</Value>
    </MyEntry>
    <MyEntry>
        <Key>key2</Key>
        <Value>value2</Value>
    </MyEntry>
</MyMap>

我见过@JacksonXmlElementWrapper,但它似乎不允许命名实体的XML元素,而是命名Map的XML元素。我不知道这是一个bug还是预期的行为。但我希望有办法解决。
我使用的演示代码以防有人想复制

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.*;
import lombok.*;
import org.junit.jupiter.api.Test;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

public class ReproductionTest {

    private static final String XML = """
<MyRoot>
    <MyMap>
        <MyEntry><Key>key1</Key><Value>value1</Value></MyEntry>
        <MyEntry><Key>key2</Key><Value>value2</Value></MyEntry>
        <!--33Key3>Not an option since I don't control keys to have valid XML names</33Key3-->
    </MyMap>
    <MyString>abc</MyString>
</MyRoot>""";

    @NoArgsConstructor
    @Getter
    @Setter
    @ToString
    static class MyRoot {
        @JacksonXmlProperty(localName = "MyMap")
        // With the next line uncommented, the following exception is thrown:
        // UnrecognizedPropertyException: Unrecognized field "MyMap"...
        // not marked as ignorable (2 known properties: "MyString", "MyEntry"])
        //@JacksonXmlElementWrapper(localName = "MyEntry")
        Map<String, String> myMap;
        @JacksonXmlProperty(localName = "MyString")
        String myString;
    }

    @Test
    public void reproducesDeserializationIssue() throws JsonProcessingException {
        XmlMapper xmlMapper = new XmlMapper();
        MyRoot myRoot = xmlMapper.readValue(XML, MyRoot.class);
        xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); // re-enforcing default
        assertEquals("abc", myRoot.getMyString());          // passes
        assertEquals(1, myRoot.getMyMap().size());          // passes
        assertNotNull(myRoot.getMyMap().get("MyEntry"));    // passes, unexpectedly
        assertEquals("", myRoot.getMyMap().get("MyEntry")); // passes, unexpectedly
        assertNull(myRoot.getMyMap().get("key1"));          // passes, unexpectedly
        assertNull(myRoot.getMyMap().get("key2"));          // passes, unexpectedly
        System.out.println(xmlMapper.writeValueAsString(myRoot));
        // Prints <MyRoot><myMap><myItem></myItem></myMap><myString>abc</myString></MyRoot>
    }
}

P.S.在这一点上,我不能控制XML模式,也不能输出POJO。但我可以控制XmlMapper配置。因此,我的理解是,我的选项仅限于MixIns和自定义反序列化器。但希望有更简单的解决办法。

ffdz8vbo

ffdz8vbo1#

正如你在你的帖子中所观察到的,问题在于这样一个事实,即通常的标准Map方式不能应用于你提供的xml,但是你可以将xml解释为MyEntry类的列表,并将其Map为列表。从初始xml开始:

<MyMap>
    <MyEntry>
        <Key>key1</Key>
        <Value>value1</Value>
    </MyEntry>
    <MyEntry>
        <Key>key2</Key>
        <Value>value2</Value>
    </MyEntry>
</MyMap>

你可以定义两个类MyRootMyEntry,如下所示:

@Data
public class MyRoot {

    //a MyEntry tag is a MyEntry element of the list
    @JacksonXmlProperty(localName = "MyEntry")
    @JacksonXmlElementWrapper(useWrapping = false)
    List<MyEntry> myMap;
}

@Data
public class MyEntry {

    @JacksonXmlProperty(localName = "Key")
    private String key;
    @JacksonXmlProperty(localName = "Value")
    private String value;
}

你可以注意到,现在myMap是一个List示例,它包含了MyEntry对象,现在你可以用预期的结果来格式化xml:

XmlMapper xmlMapper = new XmlMapper();
MyRoot myRoot = xmlMapper.readValue(xml, MyRoot.class);
//ok, it prints MyRoot(myMap=[MyEntry(key=key1, value=value1), MyEntry(key=key2, value=value2)])
System.out.println(myRoot);

相关问题