JAXB/Moxy 解组将所有字段值分配给 Map<String,Object> 而不是为其提供的特定字段

JAXB/Moxy Unmarshalling assigns all field values to Map<String,Object> rather than the specific field provided for it

简而言之,我想执行 unmarshalling as mentioned here 但是除了 Map 我还会有一个 @XmlElement。所以一个字段用 (Map field) @XmlPath(".") 注释,另一个字段用 (String field) @XmlElement 注释,然后我想执行 unmarshalling.

我的应用程序的主要目标是使用 JAXB/Moxy and Jackson 库转换 XML->JSONJSON->XML。我正在尝试 unmarshal XML 并将其映射到 Java POJO。我的 XML 可以有一些专用元素和一些随机出现的 user-defined 元素,所以我想将它们存储在 Map<String, Object> 中。因此,我正在使用 XMLAdapter。我正在关注 blog article to do so。我做的不是完全一样,而是有点不同。

我面临的问题是在 unmarshalling 期间根本没有考虑专用字段。所有的值都是 unmarshalledMap<String.Object>。据我了解,这是由于注释 @XmlPath(".")XMLAdapter 的使用而发生的,但是如果我删除此注释,它将无法按预期工作。有人可以帮我解决这个问题吗? marshaling 适用于 @XmlPath(".")XMLAdapter。该问题仅在 unmarshalling.

期间出现

以下是我要转换为 JSONXML:(注意:NameAge 是专用字段,othersuser-defined 字段。)

<Customer xmlns:google="https://google.com">
  <name>BATMAN</name>
  <age>2008</age>
  <google:main>
    <google:sub>bye</google:sub>
  </google:main>
</Customer>

以下是我的 Customer class 被 MoxyJackson 用于 marshaling, unmarshalling: (注意: NameAge 是专用字段, othersuser-defined 字段。我希望 others 仅存储无法直接映射到 POJO 的值,例如 google:main 及其上面的 children XML)

@XmlRootElement(name = "Customer")
@XmlType(name = "Customer", propOrder = {"name", "age", "others"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
  private String name;
  private String age;

  @XmlPath(".")
  @XmlJavaTypeAdapter(TestAdapter.class)
  private Map<String, Object> others;
  //Getter, Setter and other constructors
}

以下是我的 TestAdapter class,它将用于 Userdefined 字段:

class TestAdapter extends XmlAdapter<Wrapper, Map<String, Object>> {

  @Override
  public Map<String, Object> unmarshal(Wrapper value) throws Exception {
    System.out.println("INSIDE UNMARSHALLING METHOD TEST");
    final Map<String, Object> others = new HashMap<>();

    for (Object obj : value.getElements()) {
      final Element element = (Element) obj;
      final NodeList children = element.getChildNodes();

      //Check if its direct String value field or complex
      if (children.getLength() == 1) {
        others.put(element.getNodeName(), element.getTextContent());
      } else {
        List<Object> child = new ArrayList<>();
        for (int i = 0; i < children.getLength(); i++) {
          final Node n = children.item(i);
          if (n.getNodeType() == Node.ELEMENT_NODE) {
            Wrapper wrapper = new Wrapper();
            List childElements = new ArrayList();
            childElements.add(n);
            wrapper.elements = childElements;
            child.add(unmarshal(wrapper));
          }
        }
        others.put(element.getNodeName(), child);
      }
    }

    return others;
  }

  @Override
  public Wrapper marshal(Map<String, Object> v) throws Exception {
    Wrapper wrapper = new Wrapper();
    List elements = new ArrayList();
    for (Map.Entry<String, Object> property : v.entrySet()) {
      if (property.getValue() instanceof Map) {
        elements.add(new JAXBElement<Wrapper>(new QName(property.getKey()), Wrapper.class, marshal((Map) property.getValue())));
      } else {
        elements.add(new JAXBElement<String>(new QName(property.getKey()), String.class, property.getValue().toString()));
      }
    }
    wrapper.elements = elements;
    return wrapper;
  }
}

@Getter
class Wrapper {

  @XmlAnyElement
  List elements;
}

最后,我的 Main class 将用于 marshalingunmarshalling。此外,转换为 JSON 和 XML.

class Main {

  public static void main(String[] args) throws JAXBException, XMLStreamException, JsonProcessingException {

    //XML to JSON
    JAXBContext jaxbContext = JAXBContext.newInstance(Customer.class);
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("Customer.xml");
    final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
    final XMLStreamReader streamReader = xmlInputFactory.createXMLStreamReader(inputStream);
    final Customer customer = unmarshaller.unmarshal(streamReader, Customer.class).getValue();
    final ObjectMapper objectMapper = new ObjectMapper();
    final String jsonEvent = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
    System.out.println(jsonEvent);

    //JSON to XML
    Marshaller marshaller = jaxbContext.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    marshaller.marshal(customer, System.out);
  }
}

当我转换 XML->JSON 时,我得到以下输出:(如果您观察到 nameage 字段未被视为 [=46= 中的专用字段] class 而不是将其作为随机字段写入 others)

{
  "name" : "",
  "age" : "",
  "others" : {
    "google:main" : [ {
      "google:sub" : "bye"
    } ],
    "name" : "BATMAN",
    "age" : "2008"
  }
}

我希望我的输出是这样的:(我希望首先映射我的专用字段,然后如果有任何未知字段则稍后在 others MAP 中映射它们)。请注意,我不想在我的 JSON 中获得 others 标签。我只想获取专用字段的字段名称。

{
  "name": "BATMAN",
  "age": 2008,
  "google:main": {
    "google:sub": "bye"
  }
}

以下是我想在 marshaling 期间获得的 XML。另外,请注意我正在使用 @XmlPath("."),这样我在 marshaling.

期间不会在 XML 中获得 others 节点
<Customer>
    <name>BATMAN</name>
    <age>2008</age>
    <google:main>>
        <google:sub>bye</google:sub>
    </google:main>
</Customer>

marshaling 工作正常。问题发生在 .unmarshaling 期间,据我所知,这是由于带有 XMLAdapter 的注释 @XmlPath(".") 而发生的,但是如果我删除此注释,它将无法按预期工作。有人可以帮我解决这个问题吗?

** 已编辑 **

我想到了一些解决方法,但似乎对我没有任何作用。由于 @XmlPath("."),他们变得一团糟。仍在寻找一些想法或解决方法。任何帮助将不胜感激。

我认为这与以下问题有些相关:

根据漏洞单:

org.eclipse.persistence.internal.oxm.record.UnmarshalRecordImpl 
public XPathNode getNonAttributeXPathNode(String namespaceURI, String localName, String qName, Attributes attributes) {
...
Line 1279
       if(null == resultNode && null == nonPredicateNode) {
          // ANY MAPPING
          resultNode = xPathNode.getAnyNode();// by default it return the EventAdapter returing a null at this place fix the problem, but i dont know if its the best solution
       }

作为(仅)解决方法,专用字段可以转到单独的 class,因此它可以自行解组。 “客户”将从这个 class.

扩展
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Person", propOrder = { "name", "age"})
public class Person {

    protected String name;
    protected String age;
//getters and stuff
}

客户

@XmlRootElement(name = "Customer")
@XmlType(name = "Customer") //, propOrder = { "name", "age", "others" }
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer extends Person{
    @XmlPath(".")
    @XmlJavaTypeAdapter(TestAdapter.class)
    private Map<String, Object> others;
    
}

自定义适配器无法解析没有命名空间的元素或排除添加到 Person class 的特定命名空间。

@Override
public Map<String, Object> unmarshal(Wrapper value) throws Exception {
    System.out.println("INSIDE UNMARSHALLING METHOD TEST");
    final Map<String, Object> others = new HashMap<>();

    for (Object obj : value.getElements()) {
        final Element element = (Element) obj;
        final NodeList children = element.getChildNodes();
        System.out.println(element.getNodeName()+ " children.getLength(): " + children.getLength() + ", ns: " + element.getNamespaceURI());

        if (element.getNamespaceURI() != null) {
            // Check if its direct String value field or complex
            if (children.getLength() == 1) {
                others.put(element.getNodeName(), element.getTextContent());
            } else {
                List<Object> child = new ArrayList<>();
                for (int i = 0; i < children.getLength(); i++) {
                    final Node n = children.item(i);
                    if (n.getNodeType() == Node.ELEMENT_NODE) {
                        Wrapper wrapper = new Wrapper();
                        List<Object> childElements = new ArrayList<Object>();
                        childElements.add(n);
                        wrapper.elements = childElements;
                        child.add(unmarshal(wrapper));
                    }
                }
                others.put(element.getNodeName(), child);
            } 
        }
    }

    return others;
}

最后,解析将使用 DOM 对象,因此输入被读取一次

public static void main(String[] args) throws Exception {

    // XML to JSON
    jakarta.xml.bind.JAXBContext jaxbContext = JAXBContext.newInstance(Person.class, Customer.class);
    jakarta.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    File initialFile = new File("src/main/resources/Customer.xml");

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder db = dbf.newDocumentBuilder();
    Document doc = db.parse(initialFile);

    final Person person = unmarshaller.unmarshal(doc, Person.class).getValue();
    final Customer customer = unmarshaller.unmarshal(doc, Customer.class).getValue();
    System.out.println("unmarshall:" + customer);

    customer.setName(person.getName());
    customer.setAge(person.getAge());

    final ObjectMapper objectMapper = new ObjectMapper();
    final String jsonEvent = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
    System.out.println(jsonEvent);

    // JSON to XML
    Marshaller marshaller = jaxbContext.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    marshaller.marshal(customer, System.out);
}

啊,总算松了一口气。这个问题让我很头疼,但我终于找到了解决方法。尝试了很多东西并联系了很多人,但似乎没有任何效果,我认为这是 JAXB/Moxy 库的问题。我找到了解决方法。希望它对以后的人有帮助,不要像我一样沮丧:)

我使用了 2 个字段,一个带有 @XmlAnyElement(lax=true) List<Object> 用于在 marshaling 期间存储元素,另一个 Map<String, Object> 用于 JSON 的自定义序列化。除此之外,我了解到我们可以使用 beforeMarshalafterMarshalbeforeUnmarshalafterMarshal 方法。这个名字本身就暗示了它的作用。

在我的例子中,我使用 beforeMarshal 方法将来自 Map<String, Object> 的未知数据添加到 List<Object>,因此在 List<Object>marshaling 值期间] 将会被使用。我删除了 XMLAdapter.

此外,afterUnmarshal 方法将从 List<Object> 读取的未知元素添加到 Map<String, Object>,因此 Jackson 可以利用它并写入 JSON 使用CustomSearlizer.

基本上,这是一种捉迷藏的方法。 List<Object> 将在 unmarshallingmarshaling 期间被 JAXB/Moxy 使用。 Map<String, Object> 将在 Jacksonserialization and deserialization 期间使用。

Custome.class 与我的 beforeMarshalafterUnmarshalling:安排。你可以根据需要进行更改)

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@XmlRootElement(name = "Customer")
@XmlType(name = "Customer", propOrder = {"name", "age", "otherElements"})
@XmlAccessorType(XmlAccessType.FIELD)
@Getter
@Setter
@AllArgsConstructor
@ToString
public class Customer {
    @XmlTransient
    private String isA;
    private String name;
    private String age;

    @XmlAnyElement(lax = true)
    @JsonIgnore
    private List<Object> otherElements = new ArrayList<>();


    @JsonIgnore
    @XmlTransient
    private Map<String, Object> userExtensions = new HashMap<>();

    @JsonAnyGetter
    @JsonSerialize(using = CustomExtensionsSerializer.class)
    public Map<String, Object> getUserExtensions() {
        return userExtensions;
    }

    @JsonAnySetter
    public void setUserExtensions(String key, Object value) {
        userExtensions.put(key, value);
    }

    private void beforeMarshal(Marshaller m) throws ParserConfigurationException {
        System.out.println("Before Marshalling User Extension: " + userExtensions);
        ExtensionsModifier extensionsModifier = new ExtensionsModifier();
        otherElements = extensionsModifier.Marshalling(userExtensions);
        System.out.println("Before Marshalling Final Other Elements " + otherElements);
        userExtensions = new HashMap<>();
    }

    private void afterUnmarshal(Unmarshaller m, Object parent) throws ParserConfigurationException {
        System.out.println("After Unmarshalling : " + otherElements);
        ExtensionsModifier extensionsModifier = new ExtensionsModifier();
        userExtensions = extensionsModifier.Unmarshalling(otherElements);
        otherElements = new ArrayList();
    }
}

然后 ExtensionsModifier.class 将被 beforeMarshalafterUnmarshalling 方法调用:

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ExtensionsModifier {
    private javax.xml.parsers.DocumentBuilderFactory documentFactory;
    private javax.xml.parsers.DocumentBuilder documentBuilder;
    private org.w3c.dom.Document document;

    public ExtensionsModifier() throws ParserConfigurationException {
        documentFactory = DocumentBuilderFactory.newInstance();
        documentBuilder = documentFactory.newDocumentBuilder();
        document = documentBuilder.newDocument();
    }

    public List<Object> Marshalling(Map<String, Object> userExtensions) throws ParserConfigurationException {
        if (userExtensions == null) {
            return null;
        }
        List<Object> tempElement = new ArrayList<>();

        for (Map.Entry<String, Object> property : userExtensions.entrySet()) {
            Element root = document.createElement(property.getKey());
            if (property.getValue() instanceof Map) {
                List<Object> mapElements = Marshalling((Map<String, Object>) property.getValue());
                mapElements.forEach(innerChildren -> {
                    if (innerChildren instanceof Element) {
                        if (((Element) innerChildren).getTextContent() != null) {
                            root.appendChild(document.appendChild((Element) innerChildren));
                        }
                    }
                });
                tempElement.add(root);
            } else if (property.getValue() instanceof String) {
                root.setTextContent(((String) property.getValue()));
                tempElement.add(root);
            } else if (property.getValue() instanceof ArrayList) {
                for (Object dupItems : (ArrayList<Object>) property.getValue()) {
                    if (dupItems instanceof Map) {
                        Element arrayMap = document.createElement(property.getKey());
                        List<Object> arrayMapElements = Marshalling((Map<String, Object>) dupItems);
                        arrayMapElements.forEach(mapChildren -> {
                            if (mapChildren instanceof Element) {
                                if (((Element) mapChildren).getTextContent() != null) {
                                    arrayMap.appendChild(document.appendChild((Element) mapChildren));
                                }
                            }
                        });
                        tempElement.add(arrayMap);
                    } else if (dupItems instanceof String) {
                        Element arrayString = document.createElement(property.getKey());
                        arrayString.setTextContent((String) dupItems);
                        tempElement.add(arrayString);
                    }
                }
            }
        }
        return tempElement;
    }

    public Map<String, Object> Unmarshalling(List<Object> value) {
        if (value == null) {
            return null;
        }
        final Map<String, Object> extensions = new HashMap<>();
        for (Object obj : value) {
            org.w3c.dom.Element element = (org.w3c.dom.Element) obj;
            final NodeList children = element.getChildNodes();

            //System.out.println("Node Name : " + element.getNodeName() + " Value : " + element.getTextContent());
            List<Object> values = (List<Object>) extensions.get(element.getNodeName());

            if (values == null) {
                values = new ArrayList<Object>();
            }

            if (children.getLength() == 1) {
                values.add(element.getTextContent());
                extensions.put(element.getNodeName(), values);
            } else {
                List<Object> child = new ArrayList<>();
                for (int i = 0; i < children.getLength(); i++) {
                    final Node n = children.item(i);
                    if (n.getNodeType() == Node.ELEMENT_NODE) {
                        List<Object> childElements = new ArrayList();
                        childElements.add(n);
                        values.add(Unmarshalling(childElements));
                        child.add(Unmarshalling(childElements));

                    }
                }
                extensions.put(element.getNodeName(), values);
            }
        }
        return extensions;
    }
}

以下是我的 CustomSearlizerJackson 将使用它来创建 JSON:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;

public class CustomExtensionsSerializer extends JsonSerializer<Map<String, Object>> {

    private static final ObjectMapper mapper = new ObjectMapper();

    @Override
    public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        System.out.println("Custom Json Searlizer: " + value);
        recusiveSerializer(value, gen, serializers);
    }

    public void recusiveSerializer(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        for (Map.Entry<String, Object> extension : value.entrySet()) {
            if (extension.getValue() instanceof Map) {
                //If instance is MAP then call the recursive method
                recusiveSerializer((Map) extension.getValue(), gen, serializers);
            } else if (extension.getValue() instanceof String) {
                //If instance is String directly add it to the JSON
                gen.writeStringField(extension.getKey(), (String) extension.getValue());
            } else if (extension.getValue() instanceof ArrayList) {
                //If instance if ArrayList then loop over it and add it to the JSON after calling recursive method
                //If size more than 1 add outer elements
                if (((ArrayList<Object>) extension.getValue()).size() > 1) {
                    gen.writeFieldName(extension.getKey());
                    gen.writeStartObject();
                    for (Object dupItems : (ArrayList<Object>) extension.getValue()) {
                        if (dupItems instanceof Map) {
                            recusiveSerializer((Map) dupItems, gen, serializers);
                        } else {
                            gen.writeStringField(extension.getKey(), (String) dupItems);
                        }
                    }
                    gen.writeEndObject();
                } else {
                    for (Object dupItems : (ArrayList<Object>) extension.getValue()) {
                        if (dupItems instanceof Map) {
                            gen.writeFieldName(extension.getKey());
                            gen.writeStartObject();
                            recusiveSerializer((Map) dupItems, gen, serializers);
                            gen.writeEndObject();
                        } else {
                            gen.writeStringField(extension.getKey(), (String) dupItems);
                        }
                    }
                }
            }
        }
    }
}

如果我提供如下输入 XML:

<Customer xmlns:google="https://google.com">
    <name>Rise Against</name>
    <age>2000</age>
    <google:main>
        <google:sub>MyValue</google:sub>
        <google:sub>MyValue</google:sub>
    </google:main>
</Customer>

然后我得到以下 JSON 作为输出:

{
  "isA" : "Customer",
  "name" : "Rise Against",
  "age" : "2000",
  "google:main" : {
    "google:sub" : "MyValue",
    "google:sub" : "MyValue"
  }
}

反之亦然。希望说清楚如果没有留下评论会尽量回复。