问题
I have a given set of classes to unmarshall a xml into an object tree. Now I get a extended xml and want to replace one class in the object tree with an extended version.
XML file
<?xml version="1.0" encoding="UTF-8"?>
<RootNode_001>
<systemId>on the 4</systemId>
<systemName>give me some more</systemName>
<person>
<firstname>James</firstname>
<lastname>Brown</lastname>
<address>
<street>Funky</street>
<city>Town</city>
<type>HOME</type> <!-- this is the new field -->
</address>
</person>
</RootNode_001>
I create a new address class with the new field like this:
public class ExtAddress extends Address {
// inherit from address and add new field
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Now I try to unmarshall into an object tree and I expect ExtAddress
to be part of the tree like this:
public class Runner {
public static void main ( String argv[] ) throws Exception {
File file = new File( "basic.xml" );
Class cls = RootNode.class;
// create a context with the root node and the replacement class
JAXBContext jaxbContext = JAXBContext.newInstance( ExtAddress.class, cls );
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
JAXBElement jaxbElement = unmarshaller.unmarshal( new StreamSource( file ), cls );
RootNode rootNode = (RootNode) jaxbElement.getValue();
System.out.println( rootNode.getClass().getName() );
System.out.println( rootNode.getPerson().getClass().getName() );
// this returns Address but I want ExtAddress
System.out.println( rootNode.getPerson().getAddress().getClass().getName() );
}
}
I use no annotations so far. The unmarshalled object tree return Address
and not ExtAddress
. If I add a XmlType
annotation I get an exception:
@XmlTyp( name = "address" )
ExtAddress extends Address {
...
}
Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
Two classes have the same XML type name "address". Use @XmlType.name and @XmlType.namespace to assign different names to them.
this problem is related to the following location:
at jaxb.standard.Address
at jaxb.ExtAddress
I tried many things but this seems to be very near to a solution. How can I tell jaxb to use an inherited class instead of the original one.
I want to delvier a set of standard classes in a library and be able to change the object tree later as an extension when new fields in xml appear.
回答1:
Changing the xml with extAddress instead address will do the trick.
回答2:
You should do XML schema versioning instead of this, however it is difficult to do it well. There is a pdf in the linked article, you should read it.
I did the "Option 3 Change the schema's targetNamespace" in the past, in this way, you can do the following:
you have different targetNamespaces for every version:
targetNamespace="http://www.exampleSchema.com/v1.0"
targetNamespace="http://www.exampleSchema.com/v1.1"
...
and you can generate the necessary JAXB classes for each namespace in different group:
com.example.schema.generated.v10
com.example.schema.generated.v11
...
From here it is up to you how to try to read it:
- you can make a contract, that the file must ends with the version of the schema: file1_v10.xml, file2_v11.xml
- you can try to read with the latest JAXB classes, if it fails, the previous and so on
- you can the first n. line as text of the file, and parse the targetNamespace, and apply the right JAXB
- or you have another option and so on...
(of course, you have to handle each version separately after unmarshalling, which is not necessarily bad)
OR
you can decide so that you are using only 1 schema, and one can only extend the schema with optional (minOccurs="0") elements, it is ugly, and has some limitations, but sometimes it is enough.
OR
Or, you can do it in a different way: you can use another XML framework, that supports schema versioning, like this one: JMRI or JiBX (They are not necessary current or actively developed, just wanted to show some examples. What I really wanted to link is an another one, but unfortunately I forgot its name).
回答3:
A similar topic is discussed here: JAXB inheritance, unmarshal to subclass of marshaled class
With jackson 2.8.6 you can just write something like
@Test
public void readXmlToSelfDefinedPojo2() throws Exception {
ObjectMapper mapper = new XmlMapper();
ExtAdress pojo = mapper.readValue(
Thread.currentThread().getContextClassLoader().getResourceAsStream("52109685.xml"),
ExtAdress.class);
System.out.println(toString(pojo));
}
public String toString(Object obj) throws JsonGenerationException, JsonMappingException, IOException{
StringWriter w = new StringWriter();
new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true).writeValue(w, obj);
return w.toString();
}
It will print
{
"systemId" : "on the 4",
"type" : "test"
}
for
<?xml version="1.0" encoding="UTF-8"?>
<RootNode_001>
<systemId>on the 4</systemId>
<systemName>give me some more</systemName>
<type>test</type>
<person>
<firstname>James</firstname>
<lastname>Brown</lastname>
<address>
<street>Funky</street>
<city>Town</city>
<type>HOME</type> <!-- this is the new field -->
</address>
</person>
</RootNode_001>
and
@JsonIgnoreProperties(ignoreUnknown = true)
public class ExtAdress extends Adress {
public String type;
public ExtAdress() {
}
}
and
@JsonIgnoreProperties(ignoreUnknown = true)
public class Adress {
public String systemId;
public Adress() {
}
}
来源:https://stackoverflow.com/questions/52109685/jaxb-how-to-replace-a-class-binding-in-given-object-tree-while-unmarshalling