【问题标题】:JAXB @XmlAnyElement with XmlAdapter does not make a call to the unmarshal method带有 XmlAdapter 的 JAXB @XmlAnyElement 不会调用解组方法
【发布时间】:2021-08-10 08:16:25
【问题描述】:

我正在尝试解组 XML 并将其映射到 Java POJO。我的 XML 可以包含一些用户定义的元素,这些元素可以是随机的,所以我想存储它们。经过研究,我发现我可以使用@XmlAnyElement(lax=true)。我正在尝试将XMLAdapter@XmlAnyElement 一起使用,但由于某种原因,我的XMLAdapter 中的unmarshal 方法根本没有被调用,因为我无法映射user-defined 字段。

有人可以向我解释如何将unmarshaluser-defined 字段添加到我的Map<String,Object> 以获取marshalling,一切正常,我正在使用mentioned here 方法。但是unmarshalling 根本没有被调用,这让我有点困惑。

下面是我的XML,必须是unmarshalled。 (请注意,namespace 可以是动态的和用户定义的,可能会在每个 xml 中更改):

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

下面是我的Customer 类,XML 需要是unmarshalled

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

  private String name;

  @XmlAnyElement(lax = true)
  @XmlJavaTypeAdapter(TestAdapter.class)
  private Map<String, Object> others = new HashMap<>();
  
  //Getter Setter and other constructors
}

以下是我的XMLAdapter (TestAdapter) 类,它将用于marshallingunmarshalling user-defined 字段。 unmarshalling 方法根本没有被调用。但是 marshalling 方法基于 the code provided here 可以正常工作。

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

  @Override
  public Map<String,Object> unmarshal(Wrapper value) throws Exception {
    //Method not being called at all the following SYSTEM.OUT is NOT PRINTED
    System.out.println("INSIDE UNMARSHALLING METHOD TEST");
    System.out.println(value.getElements());
    return null;
  }

  @Override
  public Wrapper marshal(Map<String,Object> v) throws Exception {
    return null;
  }
}

class Wrapper {
  @XmlAnyElement
  List elements;
}

我使用了package-info.java 文件,里面填充了以下内容:

@jakarta.xml.bind.annotation.XmlSchema(namespace = "http://google.com", elementFormDefault = jakarta.xml.bind.annotation.XmlNsForm.QUALIFIED)
package io.model.jaxb;

我进行了很多研究,但找不到任何类似的答案。此外,尝试了很多东西,但都没有奏效。因此,在这里发布相同的内容并寻找一些建议或解决方法。

关于unmarshalling,我几乎没有疑问:

  1. 为什么我的 XMLAdapter TestAdapter.class unmarshal 方法在解组期间没有被调用?
  2. 我如何unmarshal 可以随命名空间随机出现的 XML 字段?
  3. 是我做错了什么还是我应该做些什么来读取动态显示的命名空间和元素?
*** FOLLOWING IS EDITED SECTION BASED ON RESPONSE FROM Thomas Fritsch ****

根据回复,我已经编辑了我的课程,但仍无法按预期工作:

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

  private String name;

  @JsonIgnore
  @XmlJavaTypeAdapter(TestAdapter.class)
  private List<Object> others;

  @XmlTransient
  @XmlAnyElement(lax = true)
  private List<Element> another = new ArrayList<>();
}
 

所以发生的情况是,如果我使用@XmlTransient,那么another 字段将不会在unmarshalling 期间填充,我需要保留它@XmlTransient,因为我不希望在marshalling 期间同样使用它我为Map&lt;String, Object&gt; others 制作了@JsonIgnore,因为在unmarshalling 期间我不需要它,但两者相互冲突,无法获得所需的输出。

我的主要目标是将XML 文件转换为JSON,反之亦然。对于上面提到的XML 文件,我想在JSON 中获得以下输出:

{
  "Customer": {
    "name": "BATMAN",
    "google:main": {
      "google:sub": "bye"
    }
  }
}

同样,当我转换这个JSON 时,我应该得到原始的XML

以下是我的Main.class:

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);
  }
}

【问题讨论】:

    标签: java xml jaxb unmarshalling moxy


    【解决方案1】:

    您的others 属性的Map 类型不适用于@XmlAnyElement

    根据其 javadoc @XmlAnyElement annotation 旨在与List 或数组属性一起使用 (通常使用Listorg.w3c.dom.Element 数组)。 可能您已将其与@XmlAnyAttribute annotation 混淆了 这确实与 Map 属性一起使用。

    因此,在您的 Customer 类中,不使用适配器的 others 属性将如下所示: 姓名;

        @XmlAnyElement(lax = true)
        private List<Element> others = new ArrayList<>();
    

    使用适配器的others 属性应该如下所示:

        @XmlAnyElement(lax = true)
        @XmlJavaTypeAdapter(TestAdapter.class)
        private List<MyObject> others = new ArrayList<>();
    

    这样做时,JAXB 将实际调用您的适配器。 适配器的工作是在ElementMyObject 之间进行转换。

    public class TestAdapter extends XmlAdapter<Element, MyObject> {
    
        @Override
        public MyObject unmarshal(Element v) throws Exception {
            ...
        }
    
        @Override
        public Element marshal(MyObject v) throws Exception {
            ...
        } 
    }
    

    【讨论】:

    • 您好,非常感谢您抽出宝贵时间回复。也为了正确的解释。我现在明白了如何为XML 中出现的未知元素执行unmarshalling。但我有一个疑问:这是否意味着我拥有的原始TestAdapter 将有一个空的unmarshalling 方法?因为我需要那个类和marshalling 方法,因为我使用Map&lt;String, Object&gt; 作为marshalling
    • 另外,这是否意味着我需要在Customer.class 中再创建一个变量作为others 变量我需要它在Map&lt;String, Object&gt; 中,因为我在marshalling 和这是由Jackson @AnySetter 设置的,它需要在Map&lt;String, Object&gt; 类型中,在marshalling 期间我需要在该类型中提供。此外,在新的TestAdapter 中,marshalling 方法必须为空,因为我需要旧的TestAdapter 中的marshalling 方法和Map&lt;String,Object&gt;,所以我对如何在不创建额外内容的情况下处理这两个问题有点困惑Adapter.
    • @BATMAN_2008 所以你的Customer 类中可能需要这两个变量。还请记住,您可以通过@JsonIgnore 注释其中一个,以便将忽略杰克逊。并且您可以通过 @XmlTransient 注释另一个,以便 JAXB 忽略它。
    • 非常感谢您的回复。我会尽力去做,如果遇到问题我会联系你:)
    • 我尝试使用 @JsonIgnore@XmlTransient 但它会给我错误的输出。我的要求是我必须从XML-&gt;JSONJSON-&gt;XML 转换我的文件。为此,我需要为MarshallingUnmarshalling 使用相同的Customer 类。如果我在List&lt;Element&gt; others 上使用@JsonIgnore,那么Jackson 将不会在JSON 的创建过程中使用它,但如果我使用@XmlTransient,它将具有我在unmarshalling 期间填充的elementsList&lt;Element&gt; another 上然后在unmarshalling 上它不会填充信息。
    【解决方案2】:

    在尝试了很多东西之后,我能够让它工作。发布相同的解决方案,以便将来对某人有所帮助。

    我用过Project Lombok,所以你可以看到一些额外的注解比如@Getter, @Setter, etc

    Method-1:

    正如@Thomas Fritsch 所述,您必须将@XmlAnyElement(lax=true)List&lt;Element&gt; 一起使用,而我使用的是Map&lt;String, Object&gt;

    Method-2:

    您可以继续使用Map&lt;String,Object&gt; 并将@XmlPath(".") 与适配器一起使用以使其正常工作。在这里发布相同的代码:(我添加了一个额外的字段age 其他一切都保持不变)

    @XmlRootElement(name = "Customer")
    @XmlType(name = "Customer", propOrder = {"name", "age", "others"})
    @XmlAccessorType(XmlAccessType.FIELD)
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Customer {
    
      @XmlElement(name = "name")
      private String name;
    
      private String age;
    
      @XmlJavaTypeAdapter(TestAdapter.class)
      @XmlPath(".")
      private Map<String, Object> others;
    }
    

    以下是TestAdapter.class 发布的相同内容以供参考。当你 unmarhsal 时,TestAdapter 中的方法 unmarshal 将被调用,你可以做任何你需要的事情。

    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;
    }
    

    虽然它适用于特定情况,但我发现使用这种方法存在一个问题。 I have created a new post for this issue. 如果我得到任何关于该问题的回复,我会尝试更新代码以使其正常工作。

    【讨论】:

      猜你喜欢
      • 2013-12-18
      • 2021-07-21
      • 2012-06-22
      • 1970-01-01
      • 1970-01-01
      • 2021-08-23
      • 1970-01-01
      • 2011-10-14
      • 2012-10-21
      相关资源
      最近更新 更多