Is it possible to "intercept" the unmarshalling process of JAXB?
I have an xml reponse that partially should be converted to a different java fields structure:
<xml>
<X_FIELD1></X_FIELD1>
<X_FIELD2></X_FIELD2>
...
<X_FIELD11></X_FIELD11>
</xml>
In my java class, I'd prefer to unmarshal this to a List<String>, instead of 11 String fields.
public class XmlResponse {
private String X_FIELD1;
private String X_FIELD2;
//...
private String X_FIELD11;
// private List<String> xFields;
}
But is that possible?
You should use a custom xml adapter
http://docs.oracle.com/javaee/5/api/javax/xml/bind/annotation/adapters/XmlJavaTypeAdapter.html
http://docs.oracle.com/javaee/5/api/javax/xml/bind/annotation/adapters/XmlAdapter.html
apply it on the class level and implement the adapter.
Related
I have an xml response and trying to convert it to java object using Xstream library.
<products>
<com.example.model.ProductModel>
<productDefinition>
<productId>1</productId>
<productType>ASD</productType>
<price>10</price>
</productDefinition>
</com.example.model.ProductModel>
</products>
Here are my java classes for this:
#XStreamAlias("products")
public class Product {
#XStreamImplicit(itemFieldName="com.example.model.ProductModel")
private ProductModel productModel;
//getters, setters
}
public class ProductModel {
#XStreamImplicit(itemFieldName="productDefinition")
private ProductDefinition productDefinition;
//getters, setters
}
When I convert this xml to java object, Product object is not null, but the ProductModel inside of it becomes null. I guess the problem is about the full package name of the xml tag. Any suggestions about how to solve it?
Thanks.
I found the soultion, XStream aliasField method helped me solve it.
xstream.aliasField("com.example.model.ProductModel", Product.class, "productModel");
This simply means, map the "com.example.model.ProductModel" element in the xml to productModel object of Product class.
I'm attempting to deserialize an XML payload (body of a SOAP message, but nothing else), with a specific hierarchy of tags / objects. When attempting to aggregate unwrapped objects into a List, a MismatchedInputException is thrown.
Example Payload
<libraryRequest>
<libraryProfile>
<libraryId>
<libraryName>
<newBookInfo>
<bookId>...</bookId>
<bookTitle>...</bookTitle>
<datePublished>...</datePublished>
</newBookInfo>
<currentBooks>
<bookId>...</bookId>
<bookTitle>...<bookTitle>
<datePublished>...</datePublished>
</currentBooks>
<currentBooks>
<bookId>...</bookId>
<bookTitle>...<bookTitle>
<datePublished>...</datePublished>
</currentBooks>
<currentBooks>...</currentBooks>
</libraryProfile>
</libraryRequest>
Java objects are
public class LibraryRequest {
private LibraryProfile libraryProfile;
#XmlElement(name = "libraryProfile")
public LibraryProfile getLibraryProfile(){
...
}
// setters
public class LibraryProfile {
// constructors, getters & setters for primitive types
private List<BookInfo> bookInfos;
public List<BookInfo> getBookInfo(){
return this.BookInfos;
}
// rest of the functions
My issue is that I don't know how many currentBooks tags will come in the XML payload, and they don't come in a wrapper element. I need to keep track of each currentBook element, which is why I was using a Collection, but I am not able to properly fill the collection with the information contained within the currentBooks tags.
Would I be able to use JAXB to group the XML sequence into a Java Collection/List, and if not would I be able to use Jackson's XML functionality to group the unwrapped XML tags into a Java Collection?
The main goal is to use have an XML request come into a Spring Controller and have the XML sequence properly deserialized into a Java List / Collection. Any advice would help.
I'm using Spring Boot 1.5.8 (later version was giving me trouble in a different way), and Jackson version 2.9.5
This is based on the XmlElement explanation from actimem.com.
The mechanics explained:
- #XmlElement is only needed if the field name is not equal to the xml tag name.
- If you would like to rename your field newBookInfo to newestBook but without changing the xml you'd simply rename your field and annotate it with #XmlElement(name="newBookInfo")
- #XmlElementWrapper is explicitly not used to advice JAXB it should search for the list tags directly in the parent node
The XML represenation classes Book
#XmlAccessorType(XmlAccessType.FIELD)
public static class Book {
private String bookId;
private String bookTitle;
// ... accessors and toString
}
and LibraryProfile
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public static class LibraryProfile {
private String libraryId;
private String libraryName;
private Book newBookInfo;
// the trick is NOT to use #XmlElementWrapper here
private List<Book> currentBooks;
private String foobar; // just to show a tag after the list
// ... accessors
}
The input based on your question (I skipped the <libraryRequest> to keep the example short)
<libraryProfile>
<libraryId>1</libraryId>
<libraryName>library of alexandria</libraryName>
<newBookInfo>
<bookId>42</bookId>
<bookTitle>the answer</bookTitle>
</newBookInfo>
<currentBooks>
<bookId>2</bookId>
<bookTitle>the second</bookTitle>
</currentBooks>
<currentBooks>
<bookId>1</bookId>
<bookTitle>the first</bookTitle>
</currentBooks>
<foobar>test-foo</foobar>
</libraryProfile>
And here the testing class:
package com.stackoverflow.answer;
import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.StringReader;
public class Tester {
public static final String INPUT = "..."; // here goes your xml
public static void main(String[] args) throws Exception {
LibraryProfile lib = JAXB.unmarshal(new StringReader(INPUT), LibraryProfile.class);
System.out.println(lib.getLibraryName() + " currently contains");
System.out.println(lib.getCurrentBooks());
System.out.println("the newest book is: " + lib.getNewBookInfo());
}
}
The output is now
library of alexandria currently contains
[Book{bookId='2', bookTitle='the second'}, Book{bookId='1', bookTitle='the first'}]
the newest book is: Book{bookId='42', bookTitle='the answer'}
My XML:
<body>
<type>authorizationStatus</type>
<data>
<AuthorizationStatusMessage>
<id>12345679</id>
<email>abc</email>
</AuthorizationStatusMessage>
</data>
</body>
I want to unmarshal this XML to a POJO like that:
public class XMPPMessage {
private String type;
private String data;
}
Jackson unmarshal gives me a HashMap:
{AuthorizationStatusMessage={id_colaborador=12345679, email=rhochman#atech.com}}
But I want keep the data inner XML as a String like that:
<AuthorizationStatusMessage><id>12345679</id><email>abc</email></AuthorizationStatusMessage>
How can I keep the inner XML as a String??
I can see two options.
If you can modify your XML schema, you might want to leverage CDATA section (https://en.wikipedia.org/wiki/CDATA) to keep your <data> contents as String.
You can implement a custom Jackson deserializer to extract type and data only. There are plenty of good references on the web (e.g. http://www.baeldung.com/jackson-deserialization).
I got it someway... I found a library named Conversion Box to convert HashMap to XML.
I create custom Deserializer for storing inner XML as String, example on Kotlin:
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.dataformat.xml.XmlMapper
object XmlInnerDeserializer : StdScalarDeserializer<String>(String::class.java) {
override fun deserialize(jp: JsonParser?, context: DeserializationContext?): String {
val node = jp!!.codec.readTree<ObjectNode>(jp)
val xmlMapper: ObjectMapper = XmlMapper()
val xml = xmlMapper.writeValueAsString(node)
return xml.toString()
}
}
How use it:
#JacksonXmlRootElement(localName = "body")
#JsonIgnoreProperties(ignoreUnknown = true)
class XMPPMessage (
#JsonDeserialize(using = XmlInnerDeserializer::class)
#JacksonXmlProperty(localName = "data")
val data: String
}
Field data contains "<ObjectNode><AuthorizationStatusMessage>... </AuthorizationStatusMessage></ObjectNode>" string.
Well, I'm trying to parse objects and I'm having so much issues.
My classes are like this:
-Entidad-
public class Entidad{
private Long codEntidad;
private Set<Comunicacion> comunicacion;
/*------------ Getter and Setters --------------*/
}
-Comunicacion-
public class Comunicacion {
private Entidad entidad;
private Long codComunicacion;
/*------------ Getter and Setters --------------*/
}
I need to parse to DTO objects:
-EntidadDTO-
public class EntidadDTO{
private Long codEntidad;
private Set<ComunicacionDTO> comunicacionDto;
/*------------ Getter and Setters --------------*/
}
-ComunicacionDTO-
public class ComunicacionDTO {
private EntidadDto entidadDto;
private Long codComunicacion;
/*------------ Getter and Setters --------------*/
}
I tried to use:
BeanUtils.copyProperties(entidad, entidadDto);
It seems that the parse is success but the property entidadDto.getComunicacionDto(); is a hashMap of Comunicacion (not ComunicacionDTO)
Should I try to make a custom parse with reflection?
Also I'd like to use this to parse more objects with a similar structure.
Thanks!
Try dozer. You can define mappings from bean to bean using it.
http://dozer.sourceforge.net/
Why you want to parse java object and move data to other java object?
Parsing is for unstructured strings not for objects.
Use setters/getters to move data from one object to the other, using reflection will make you cry when you start doing refactorings.
I have surely a problem of understanding of Xstream converter.
I have a pretty complex XML coming from legacy back office application and a just want to convert this one into a simpler java class or a Map with Converter.
I do not want to marshal to XML, just unmarshal
I do not want to use annotation, just KISS
My XML is like
<root>
<agent>
<id>123456789</id>
<name>GABIN</name>
<forname>Jean</forname>
<unit>
<legacyReference>AA</legacyReference>
<name>SAN ANTONIO</name>
<legacyName>SA</legacyName>
<address>
<number>1</number>
<street>Sesam street</street>
<city>NoWhere</city>
<legacyID>AAZE-56</legacyID>
</address>
<legacyStructurBlablabla>
<type>OFFICE</type>
<data>
<foo>BAR</foo>
</data>
</legacyStructurBlablabla>
<...>
</unit>
<...>
</agent>
</root>
My destination class is very simple
class Agent {
String id;
String name;
String forname;
String unitName;
String unitType;
<...>
}
My main method looks like
xStream = new XStream(new DomDriver());
stream = this.getClass().getClassLoader().getResourceAsStream("data/agent.xml") ;
xStream.alias("root", Map.class);
xStream.alias("agent", Agent.class);
xStream.registerConverter(new ResultConverter());
xStream.registerConverter(new AgentConverter());
xStream.ignoreUnknownElements();
Object obj = xStream.fromXML(stream);
I don't understand how to step down in favor of another converter.
public class ResultConverter implements Converter {
...
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Map<String, Object> agents = new HashMap<String, Object>();
while( reader.hasMoreChildren() ) {
reader.moveDown();
// HOW I DO TO STEP DOWN IN FAVOR OF Agent CONVERTER ?
reader.moveUp();
}
return agents;
}
I didn't see Xstream scrolled dow the thierarchy to activate my AgentConverter.
I surely missing the point
EDIT
Xstream may be not thr right tool for this.
I would use xmlbeam, according to Cfx advise.
XMLBeam projects the XML to a Java Class according to XPATH mapping.
Seems that you want the xml structure not to be reflected in your Java class. If you stick to XStream, I have no solution. But there is an alternative framework that was made to solve exactly this issue. It uses annotations, but IMHO does not violate the KISS principle.
Here is an example:
public interface Agent {
#XBRead("/root/agent/id")
String getId();
#XBRead("/root/agent/name")
String getName();
#XBRead("/root/agent/forename")
String getForname();
#XBRead("/root/agent/unit/name")
String getUnitName();
#XBRead("/root/agent/unit/legacyStructurBlablabla/type")
String getUnitType();
}
You will define interfaces instead of classes, but the instances of these interfaces are useable just like POJOs. (with toString(), equals(), hashCode,...).
Creating instances of these interfaces is like this:
Agent agent = new XBProjector().io().stream(stream).read(Agent.class);