JAXB sort fields whose type is a typical type parameter

I would expect the following test to work with Sun JAXB RI 2.2.1.1, but instead it will complete with a NullPointerException when constructing a JAXBContext:

public class GenericFieldMarshallTest { public static class CustomType { } public static class CustomTypeAdapter extends XmlAdapter<String, CustomType> { @Override public String marshal(CustomType v) throws Exception { return "CustomType"; } @Override public CustomType unmarshal(String v) throws Exception { return new CustomType(); } } @XmlJavaTypeAdapter(type = CustomType.class, value = CustomTypeAdapter.class) public static class RootElement<ValueType> { @XmlValue public ValueType value; } @XmlRootElement(name = "root") public static class CustomRootElement extends RootElement<CustomType> { public CustomRootElement() { value = new CustomType(); } } @Test public void test() throws Exception { JAXBContext context = JAXBContext.newInstance(CustomRootElement.class, CustomType.class, RootElement.class); StringWriter w = new StringWriter(); context.createMarshaller().marshal(new CustomRootElement(), w); assertThat(w.toString(), equalTo("<root>CustomType</root>")); } } 

An exception I get:

 java.lang.NullPointerException at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor.get(TransducedAccessor.java:165) at com.sun.xml.bind.v2.runtime.property.ValueProperty.<init>(ValueProperty.java:77) at com.sun.xml.bind.v2.runtime.property.PropertyFactory.create(PropertyFactory.java:106) at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.<init>(ClassBeanInfoImpl.java:179) at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:515) at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.<init>(ClassBeanInfoImpl.java:166) at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:515) at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:330) at com.sun.xml.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1140) at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:154) at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:121) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:202) at javax.xml.bind.ContextFinder.find(ContextFinder.java:363) at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574) at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:522) 

It seems that the reason is that JAXB does not know how to marshal the declared type of my field (which, in my opinion, is erased before Object at runtime?), Although at runtime I only ever set the field for types that JAXB is aware of .

How can I marshal a field whose type is shared?

(Replacing @XmlValue with @XmlAttribute does not fix the exception and does not change the declared field type to Object, although, of course, everything works fine if the field is declared as String, except that String cannot be assigned from CustomType Location @XmlJavaTypeAdapter also it matters, in my actual code it is installed at the package level in package-info.java.)

+4
source share
1 answer

First: Your XmlAdapter incorrect. Common types are the other way around.

Then you seem to need to put @XmlJavaTypeAdapter on CustomRootElement .

In addition, JAXBContext needs to be told about all the classes involved. Create jaxb.index or ObjectFactory and create a context by specifying the package name in the newInstance method or listing all the classes.

Full code (slightly modified since I use main() , not the JUnit test method):

 public static class CustomType { } public static class CustomTypeAdapter extends XmlAdapter<String, CustomType> { @Override public String marshal(CustomType v) throws Exception { return "CustomType"; } @Override public CustomType unmarshal(String v) throws Exception { return new CustomType(); } } public static class RootElement<V> { public V value; } @XmlJavaTypeAdapter(CustomTypeAdapter.class) @XmlRootElement(name = "root") public static class CustomRootElement extends RootElement<CustomType> { public CustomRootElement() { value = new CustomType(); } } public static void main(String[] args) throws Exception { JAXBContext context = JAXBContext.newInstance(CustomRootElement.class, CustomType.class, RootElement.class); StringWriter w = new StringWriter(); CustomRootElement cre = new CustomRootElement(); cre.value = new CustomType(); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty("jaxb.formatted.output", Boolean.TRUE); marshaller.marshal(cre, w); System.err.println(w.toString()); // just to see whether unmarshalling works too CustomRootElement c = (CustomRootElement) context.createUnmarshaller() .unmarshal(new StringReader(w.toString())); System.err.println(c.value); } 

Now the result of sorting a CustomRootElement is not what you expect in your test (and this is not what I expected), but you can untie it and get what you previously planned. So, (un) sorting both works, but XML doesn't look so good:

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <root> <value xsi:type="customType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> </root> 

I also put the field in CustomType and it worked too. Therefore, if you do not need good XML, this solution should be sufficient. Hope I have not forgotten any changes I made. If I did, just leave a comment and I will edit accordingly.

+2
source

Source: https://habr.com/ru/post/1314183/


All Articles