I have the following XML that I'd like to deserialize to Java POJO.
<testdata>
<foo>
<bar>
<![CDATA[MESSAGE1]]>
</bar>
<bar>
<![CDATA[MESSAGE2]]>
</bar>
<bar>
<![CDATA[MESSAGE3]]>
</bar>
</foo>
</testdata>
I have the following Java classes
public class TestData {
#JacksonXmlProperty(localName = "foo")
private Foo foo;
public Foo getFoo() {
return foo;
}
public void setFoo(Foo foo) {
this.foo = foo;
}
}
I have another class like below
public class Foo {
#JacksonXmlProperty(localName = "bar")
#JacksonXmlCData
private List<String> barList;
public List<String> getBarList() {
return barList;
}
public void setBarList(List<String> barList) {
this.barList = barList;
}
}
Now when I run the code using the class below I get an exception
private void readXml() throws FileNotFoundException, IOException {
File file = new File("/Users/temp.xml");
XmlMapper xmlMapper = new XmlMapper();
String xml = GeneralUtils.inputStreamToString(new FileInputStream(file));
TestData testData = xmlMapper.readValue(xml, TestData.class);
System.out.println(testData.getFoo()
.getBarList());
}
Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.util.ArrayList out of VALUE_STRING token
How do I convert bar elements into a List? I tried multiple things but I keep getting some or the other errors
You need to indicate that <bar> is a wrapping element for your collection of String messages:
This should work in your Foo class:
#JacksonXmlProperty(localName = "bar")
#JacksonXmlCData
#JacksonXmlElementWrapper(useWrapping = false)
private List<String> barList;
In case you have in your input xml a list of bar elements with an attribute like
<testdata>
<foo>
<bar name="John">
<![CDATA[MESSAGE1]]>
</bar>
<bar name="Mary">
<![CDATA[MESSAGE2]]>
</bar>
<bar name="Bill">
<![CDATA[MESSAGE3]]>
</bar>
</foo>
<testdata>
you could create a Bar class and include a list of it as a field of the Foo class:
#JacksonXmlProperty(localName = "bar")
#JacksonXmlElementWrapper(useWrapping = false)
private List<Bar> barList;
The Bar class would be:
class Bar {
#JacksonXmlProperty(isAttribute = true)
private String name;
#JacksonXmlCData
private String content;
}
Remember to include getters and setters for the Bar class.
Related
I have two classes Foos and Foo that are supposed to be serializable/deserializable to/from XML:
#XmlRootElement(name = "foos")
#XmlAccessorType(XmlAccessType.NONE)
#XmlType(name = "Foos", propOrder = { "foo" })
public class Foos {
protected Map<Foo, Foo> foo;
public Map<Foo, Foo> getFooMap() {
if (foo == null) {
foo = new HashMap<Foo, Foo>();
}
return this.foo;
}
#XmlElements(value = { #XmlElement })
public Collection<Foo> getFoo() {
return getFooMap().values();
}
}
and
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Foo", propOrder = { "name" })
public class Foo {
protected String name;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
}
For algorithmical reasons I cannot use a List<Foo> but need a Map<Foo, Foo>. Each Foo should be put into the Map both as key and value.
Marshalling a Foos object to XML works because I have the getFoo getter, but unmarshalling from XML does not. This is probably because the unmarshaller does not know how to convert the xml sequence of Foo objects into a hash map.
How can I tell this to the unmarshaller?
I think I need a method like this (pseudocode):
public void fillMap(XMLSequence<Foo> sequence) {
for (Foo foo : sequence)
getFooMap().put(foo, foo);
}
And then it probably needs some xml annotations as well.
Any hints are appreciated!
I found a way using two fields:
#XmlRootElement(name = "foos")
#XmlAccessorType(XmlAccessType.NONE)
#XmlType(name = "Foos", propOrder = { "fooList", "foo" })
public class Foos {
#XmlElement(name="foo")
protected List<Foo> fooList;
protected Map<Foo, Foo> foo;
public Map<Foo, Foo> getFooMap() {
if (foo == null) {
foo = new HashMap<Foo, Foo>();
if (fooList != null)
for (Foo f : fooList)
foo.put(f, f);
}
return this.foo;
}
#XmlElement
public Collection<Foo> getFoo() {
return getFooMap().values();
}
}
During unmarshalling, the fooList is filled with the data from the <foo>...</foo> xml elements.
When getFooMap is called for the first time, it is filled with the content of fooList (by putting the same object as key and value).
During marshalling, the method getFoo is called which will return the values of the foo map.
I need to marshall a java class to get a xml, but i don't know how to delete a tag inside the one generated.
I have a class with an object list with this form
#XmlRootElement(name = "Element")
public class Element {
private List<Foo> foos;
#XmlElementWrapper("fooList")
public List<Foo> getfoos() {
return foos;
}
public void setFoos(List<Foo> foos) {
this.foos = foos;
}
}
And the class Foo of the list is lie this:
#XmlRootElement
public class Foo {
private String id;
private String code;
#XmlElement
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#XmlElement
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
When marshalling to get xml I get this:
<Element>
<fooList>
<foos>
<string1>asd</string1>
<string2>qwe</string2>
</foos>
<foos>
<string1>poi</string1>
<string2>lkj</string2>
</foos>
</fooList>
</Element>
But I want to get it without the tag foos, like this:
<Element>
<fooList>
<string1>asd</string1>
<string2>qwe</string2>
<string1>poi</string1>
<string2>lkj</string2>
</fooList>
</Element>
Can anyone help me?
Thanks a lot!!
You could do something like this:
#XmlRootElement(name = "Element")
#XmlAccessorType(XmlAccessType.FIELD)
public class Element {
#XmlElementWrapper(name = "fooList")
#XmlElements({
#XmlElement(name = "id", type = Id.class),
#XmlElement(name = "code", type = Code.class),
})
private List<FooItem> foos;
public List<FooItem> getfoos() {
return foos;
}
public void setFoos(List<FooItem> foos) {
this.foos = foos;
}
}
and then Id and Code classes look similar:
public class Id implements FooItem {
#XmlValue
private String id;
public Id() {}
public Id(String id) {
this.id = id;
}
}
They are bounded by an interface that doesn't do much:
public interface FooItem { }
This structure will allow you to marshal into xml as the one you specified you need.
The challenge with the class structure you had is that class Foo had 2 fields and #XmlValue can be applied only to one field per class. So having 2 fields "forces" them to stand for #XmlElement and they in turn have to be children of an xml element. This is why you had the "intermediate" foo elements in your xml for each Foo instance in your List.
I have a xml with content below:
<ParentClass>
<StringA>A</StringA>
<StringB>B</StringB>
<Items>
<Item ts="2016-03-25T20:00:00+02:00">1.17</Item>
<Item ts="2016-03-25T21:00:00+02:00">1.15</Item>
</Items>
</ParentClass>
I would like to read it but I got stuck on proper mapping. Classes are as below:
#XmlAccessorType(XmlAccessType.FIELD)
#NoArgsConstructor
#AllArgsConstructor
#Data
public class ParentClass {
#XmlElement(name = "StringA", required = true)
private String a;
#XmlElement(name = "StringB", required = true)
private String b;
#XmlElement(name = "Items", required = true)
private List<Item> consumptionList;
}
#NoArgsConstructor
#AllArgsConstructor
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class Item {
#XmlAttribute(name = "ts", required = true)
#XmlJavaTypeAdapter(value = LocalDateTimeAdapter.class)
private LocalDateTime timestamp;
private double value; //this corrensponds to 1.17 and 1.15 in xml
}
In the actual file there are 100+ items yet when I read it the list is populated with only one instance of Item class and it has both fields null.
I guess the mapping in the Item class is all wrong but tried all and nothing seems to work.
How should I properly map it to achieve the goal?
You need to add #XmlValue to field value, otherwise it defaults to #XmlElement.
Also, you need to change the annotations on consumptionList to
#XmlElementWrapper(name = "Items")
#XmlElement(name = "Item", required = true)
Also be aware that the ts values are OffsetDateTime (or ZonedDateTime) values, not LocalDateTime, unless your LocalDateTimeAdapter applies a time zone, e.g. the JVM default time zone.
I've found that the best way to help apply #Xml... annotations correctly, is to create objects and marshal them to XML to see what you get. Your current code will create this XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ParentClass>
<StringA>A</StringA>
<StringB>B</StringB>
<Items ts="2016-03-25T20:00:00+02:00">
<value>1.17</value>
</Items>
<Items ts="2016-03-25T21:00:00+02:00">
<value>1.15</value>
</Items>
</ParentClass>
If you apply the changes mentioned above, you get:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ParentClass>
<StringA>A</StringA>
<StringB>B</StringB>
<Items>
<Item ts="2016-03-25T20:00:00+02:00">1.17</Item>
<Item ts="2016-03-25T21:00:00+02:00">1.15</Item>
</Items>
</ParentClass>
The above outputs were created by adding this code:
class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {
#Override
public String marshal(LocalDateTime time) throws Exception {
return time.atZone(ZoneOffset.ofHours(2))
.format(DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssxxx"));
}
#Override
public LocalDateTime unmarshal(String text) throws Exception {
return ZonedDateTime.parse(text).toLocalDateTime();
}
}
public static void main(String... args) throws Exception {
ParentClass p = new ParentClass("A", "B", Arrays.asList(
new Item(LocalDateTime.parse("2016-03-25T20:00:00"), 1.17),
new Item(LocalDateTime.parse("2016-03-25T21:00:00"), 1.15)));
JAXBContext jaxbContext = JAXBContext.newInstance(ParentClass.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(p, System.out);
}
I want to give custom names to the xml root element and to list elements.
But the annotations don't work.
#XmlRootElement(name = "test")
#XmlAccessorType(XmlAccessType.FIELD)
public class TestRsp {
#XmlElementWrapper(name = "persons")
#XmlElement(name = "pax")
private List<Person> persons;
}
public class Person {
private String name;
private String age;
}
Usage:
#RestController
public class MyServlet {
#RequestMapping("/test")
public TestRsp test() {
//...
return rsp;
}
}
Result:
<TestRsp> <!-- should be named "test" -->
<persons>
<persons> <!-- should be named "pax" -->
<name />
<age />
</persons>
<persons>
//...
</persons>
</persons>
</TestRsp>
So my xml annotations are not picked up. But why?
Try using #JsonProperty annotation.
It should look something like thit:
#JsonProperty("test")
EDIT 1:
Try annotating the getter of the fields that you want to change like so:
#XmlElement(name="someName")
EDIT 2:
#XmlRootElement(name="persons")
public class Root {
private List<String> someList;
#XmlElement(name="pax")
public List<String> getSomeList() {
return someList;
}
public void setSomeList(List<String> someList) {
this.someList = someList;
}
public Root(String numValue,List<String> someListValue) {
this();
this.number = numValue;
this.someList = someListValue;
}
/**
*
*/
public Root() {
// TODO Auto-generated constructor stub
}
}
Maybe this will provide:
<persons>
<pax>FOO</pax>
<pax>BAR</pax>
</persons>
EDIT 3:
Maybe if you want to make your solution works you need to add a file called jaxb.properties in with your model classes with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
It turned out I have to use #JacksonXml* annotations instead:
#JacksonXmlRootElement(localName = "test")
public class TestRsp {
#JacksonXmlElementWrapper(localName = "persons")
#JacksonXmlProperty(localName = "pax")
#JsonProperty(name = "persons")
private List<Person> persons;
}
I have a Response class which contains some basic attributes and a wildcard Collection<?>.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Response {
private String approved;
private String errorCode;
#XmlAnyElement(lax = true)
private Collection<?> collection;
public Response() {
}
public Response(String approved, Collection<?> collection) {
this.approved = approved;
this.collection = collection;
}
public String getApproved() {
return approved;
}
public String getErrorCode() {
return errorCode;
}
public Collection<?> getCollection() {
return collection;
}
}
This collection can contain many types, for example this type:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Transaction {
private BigDecimal amount;
private String transactionId;
public Transaction(BigDecimal amount, String transactionId ) {
super();
this.amount = amount;
this.transactionId = transactionId ;
}
public Transaction() {
super();
}
public BigDecimal getAmount() {
return amount;
}
public String getTransactionId() {
return transactionId;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
}
When serializing the Response class, I get this XML.
<?xml version="1.0" encoding="UTF-8"?>
<response>
<approved>00</approved>
<errorCode></errorCode>
<transaction>
<amount>500.00</amount>
<transactionId>pgka3902</transactionId>
</transaction>
<transaction>
<amount>201.05</amount>
<transactionId>abcd3020</transactionId>
</transaction>
</response>
Adding #XmlElementWrapper wraps <transaction> elements in <collection> which is not acceptable still. I need the wrapper to be named the plural of the actual type in collection. For example, the above xml should be:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<approved>00</approved>
<errorCode />
<transactions>
<transaction>
<amount>500.00</amount>
<transactionId>pgka3902</transactionId>
</transaction>
<transaction>
<amount>201.05</amount>
<transactionId>abcd3020</transactionId>
</transaction>
</transactions>
</response>
Is it possible to do this with JAXB? I'm using Eclipselink Moxy implementation.
Instead of Response holding a Collection you could change that to Object. Then you could have different classes for each of your collection types.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Transactions {
#XmlElement(name="transaction")
private List<Transaction> transactions;
}
The #XmlElementWrapper annotation has an optional parameter: name. If not specified, by default it will be the name of the Java field which is collection in your case. That's why your wrapper tag is named <collection>.
You can specify the name of the wrapper element/tag by passing the name argument to the #XmlElementWrapper annotation like this:
#XmlElementWrapper(name="transactions")
This will result in your desired XML tags.