简体   繁体   English

JAXB @XmlAnyElement 与 XmlAdapter 不调用解组方法

[英]JAXB @XmlAnyElement with XmlAdapter does not make a call to the unmarshal method

I am trying to unmarshal the XML and map it to the Java POJO.我正在尝试将XML和 map 解组为 Java POJO。 My XML can have some of the user-defined elements which can be random so I would like to store them.我的 XML 可以有一些用户定义的元素,这些元素可以是随机的,所以我想存储它们。 After researching I found that I can use @XmlAnyElement(lax=true) .经过研究,我发现我可以使用@XmlAnyElement(lax=true) I am trying to use the XMLAdapter along with the @XmlAnyElement but for some reason, the method unmarshal within my XMLAdapter is not being called at all due to which I am unable to map the user-defined fields.我正在尝试将XMLAdapter@XmlAnyElement一起使用,但由于某种原因,我的XMLAdapter中的unmarshal组方法根本没有被调用,因此我无法 map user-defined字段。

Can someone please explain to me how to unmarshal the user-defined fields to my Map<String,Object> for the marshalling everything is working fine and I am using the approach mentioned here .有人可以向我解释如何将user-defined字段unmarshal组到我的Map<String,Object>以便marshalling一切正常,我正在使用这里提到的方法。 But unmarshalling is not being called at all which is bit confusing for me.但是根本没有调用unmarshalling ,这让我有点困惑。

Following is my XML which needs to be unmarshalled .以下是我的XML需要unmarshalled组。 (Please note that the namespace can be dynamic and user-defined which may change in every xml): (请注意, namespace可以是动态的和用户定义的,可能会在每个 xml 中更改):

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

Following is my Customer class to which XML needs to be unmarshalled ;以下是我的Customer XML需要unmarshalled组 XML ;

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

Following is my XMLAdapter (TestAdapter) class which will be used for marshalling and unmarshalling the user-defined fields.以下是我的XMLAdapter (TestAdapter) unmarshalling ,它将用于marshalling和解组user-defined字段。 The unmarshalling method is not being called at all.根本没有调用unmarshalling方法。 However the marshalling method works as expected based on the code provided here .然而,基于此处提供的代码marshalling方法按预期工作。

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

I have used the package-info.java file and it has been filled with following contents:我使用了package-info.java文件,里面填充了以下内容:

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

I researched a lot but could not find any answer which does something similar.我进行了很多研究,但找不到任何类似的答案。 Also, tried a lot of things but none worked.此外,尝试了很多东西,但都没有奏效。 Hence, posting the same here and looking for some suggestion or workaround.因此,在这里发布相同的内容并寻找一些建议或解决方法。

I have few doubts with regards to unmarshalling :关于unmarshalling ,我几乎没有疑问:

  1. Why my XMLAdapter TestAdapter.class unmarshal method is not being called during the unmarshalling?为什么在解组期间没有调用我的XMLAdapter TestAdapter.class unmarshal组方法?
  2. How can I unmarshal the XML fields which can appear randomly with namespaces?如何unmarshal组可能随命名空间随机出现的 XML 字段?
  3. Am I doing something wrong or is there something else I should do to read the namespaces and elements which appear dynamically?我做错了什么还是我应该做些什么来读取动态出现的命名空间和元素?
*** FOLLOWING IS EDITED SECTION BASED ON RESPONSE FROM Thomas Fritsch ****

Based on the response I have edited my class but still not working as expected:根据回复,我编辑了我的 class 但仍无法按预期工作:

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

So what's happening is that if I use @XmlTransient then the field another will not be populated during the unmarshalling and I need to keep it @XmlTransient because I do not want it during the marshalling similarly I have made @JsonIgnore for Map<String, Object> others because I do not need it during the unmarshalling but both things are conflicting with each other and not able to obtain the the required output.所以发生的事情是,如果我使用@XmlTransient ,那么在unmarshalling组期间将不会填充another字段,我需要保留它@XmlTransient ,因为我在marshalling @JsonIgnore中不想要它,类似地我为Map<String, Object> others ,因为我在unmarshalling组过程中不需要它,但两者相互冲突,无法获得所需的 output。

My main goal is to convert the XML file to JSON and vice versa.我的主要目标是将XML文件转换为JSON ,反之亦然。 For the above mentioned XML file I would like to obtain the following output in JSON :对于上面提到的XML文件,我想在 JSON 中获得以下JSON

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

Similarly when I convert this JSON then I should get the original XML .同样,当我转换此JSON时,我应该得到原始的XML

Following is my Main.class :以下是我的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);
  }
}

The Map type of your others property is not suitable for @XmlAnyElement .others属性的Map类型不适合@XmlAnyElement

According to the its javadoc the @XmlAnyElement annotation is meant to be used with a List or an array property (typically with a List or array of org.w3c.dom.Element ).根据其 javadoc, @XmlAnyElement注释旨在与List或数组属性一起使用(通常与Listorg.w3c.dom.Element数组一起使用)。 May be you have confused this with the @XmlAnyAttribute annotation which indeed is used with a Map property.可能您已经将此与@XmlAnyAttribute注释混淆了,后者确实与Map属性一起使用。

Hence in your Customer class the others property without using an adapter would look like this: name;因此,在您的Customer class 中,不使用适配器的others属性将如下所示:

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

And the others property with using an adapter should look like this:使用适配器的others属性应如下所示:

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

When doing this way, then JAXB will actually call your adapter.这样做时,JAXB 实际上会调用您的适配器。 The adapter's job is to transform between Element and MyObject .适配器的工作是在ElementMyObject之间进行转换。

public class TestAdapter extends XmlAdapter<Element, MyObject> {

    @Override
    public MyObject unmarshal(Element v) throws Exception {
        ...
    }

    @Override
    public Element marshal(MyObject v) throws Exception {
        ...
    } 
}

After trying out a lot of things, I was able to get it working.在尝试了很多东西之后,我能够让它工作。 Posting the solution for the same so it can be helpful to someone in the future.发布相同的解决方案,以便将来对某人有所帮助。

I have used Project Lombok so you can see some additional annotations such as @Getter, @Setter, etc我使用了Project Lombok ,因此您可以看到一些额外的注释,例如@Getter, @Setter, etc

Method-1:

As mentioned @Thomas Fritsch you have to use the @XmlAnyElement(lax=true) with List<Element> I was using with the Map<String, Object> .正如@Thomas Fritsch 所提到的,您必须将@XmlAnyElement(lax=true)List<Element>我与Map<String, Object>一起使用。

Method-2:

You can continue to use Map<String,Object> and use @XmlPath(".") with adapter to get it working.您可以继续使用Map<String,Object>并将@XmlPath(".")与适配器一起使用以使其正常工作。 Posting the code for the same here: (I have added one additional field age other that everything remain the same)在这里发布相同的代码:(我添加了一个额外的字段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;
}

Following is the TestAdapter.class posting the same for reference.以下是发布相同内容的TestAdapter.class以供参考。 When you unmarhsal the method unmarshal in TestAdapter will get called and you can do anything you need.当你解组时, unmarhsal中的unmarshal TestAdapter方法将被调用,你可以做任何你需要的事情。

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

Although it works for specific cases, I am seeing one problem by using this approach.尽管它适用于特定情况,但使用这种方法我发现了一个问题。 I have created a new post for this issue. 我为此问题创建了一个新帖子。 If I get any response for that issue then I will try to update the code so it can work correctly.如果我对此问题有任何回应,那么我将尝试更新代码,使其能够正常工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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