I need to build a parser to parse an XML file to a Java object.
I use Jackson to do this and followed the steps provided in THIS tutorial.
In the tutorial is a section 'Manipulating Nested Elements and Lists in XML'. I followed it, but unfortunately I can't get the desired output of all my required elements - I want to output first and last of all my authors. And I only get it for my last author in the XML-file like this:
[{nameList={person={first=Karl, last=S}}}]
My XML file looks like this.
<sources>
<Doi>123456789</Doi>
<Title>Title</Title>
<author>
<editor>
<nameList>
<person>
<first>Peter</first>
<last>Parker</last>
</person>
</nameList>
</editor>
</author>
<Source>
<SourceType>Book</SourceType>
<ShortTitle>Book Title</ShortTitle>
<Author>
<Editor>
<NameList />
</Editor>
</Author>
</Source>
<author>
<bookAuthor>
<nameList>
<person>
<first>Karl</first>
<last>S</last>
</person>
</nameList>
</bookAuthor>
</author>
<Source>
<SourceType>Journal</SourceType>
<ShortTitle>ABC Journal</ShortTitle>
</Source>
</sources>
How can I deserealize the entire XML file?
My code looks like this:
MyClass.java
private static void jacksonXmlFileToObject() throws IOException {
System.out.println("jacksonXmlFileToObject");
InputStream xmlFile = Publication.class.getClassLoader().getResourceAsStream("test.xml");
ObjectMapper mapper = new XmlMapper();
// Configure
mapper
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
Sources deserializedData = mapper.readValue(xmlFile, Sources.class);
System.out.println(deserializedData);
} catch (IOException e) {
e.printStackTrace();
}
}
Sources.java
#EqualsAndHashCode
#JacksonXmlRootElement(localName = "sources") public class Sources {
#JacksonXmlElementWrapper(localName = "author")
#Getter
#Setter
private Object[] author;
#Override
public String toString() {
return Arrays.toString(author);
}
public Sources() {
}
}
I would be very happy about some help.
Thank you!
Use the JsonMerge annotation.
I had a similar problem myself recently, and found out that the annotation #JsonMerge solves the problem.
I have simplified the XML a little:
<sources>
<author>
<name>Jack</name>
</author>
<source>
<type>Book</type>
</source>
<author>
<name>Jill</name>
</author>
<source>
<type>Journal</type>
</source>
</sources>
With the classes Author and Source
class Author {
String name;
}
class Source {
String type;
}
The Sources class looks as follows:
class Sources {
// We prevent each <author> tag to be wrapped in an <authors> container tag
#JacksonXmlElementWrapper(useWrapping = false)
// Each element is <author> and not <authors> (and we named our field 'authors')
#JacksonXmlProperty(localName = "author")
// This is the property which solves your problem. It causes non-subsequent elements with the
// same name to be merged into the existing list
#JsonMerge
private List<Author> authors;
#JacksonXmlElementWrapper(useWrapping = false)
#JacksonXmlProperty(localName = "source")
#JsonMerge
private List<Source> sources;
}
It looks like JacksonXmlElementWrapper does not work when the same elements are not following each other. Regular XML should contain the same nodes listed one after another. When other node starts it's mean previous node section is finished. To handle your case we need to write custom deserialiser: manually read all authors and skip the rest of nodes. Example code could look like this:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class XmlMapperApp {
public static void main(String[] args) throws Exception {
File xmlFile = new File("./resource/test.xml").getAbsoluteFile();
XmlMapper mapper = new XmlMapper();
System.out.println(mapper.readValue(xmlFile, Sources.class));
}
}
class SourcesJsonDeserializer extends JsonDeserializer<Sources> {
private final JsonPointer EDITOR = JsonPointer.compile("/editor/nameList/person");
private final JsonPointer BOOK_AUTHOR = JsonPointer.compile("/bookAuthor/nameList/person");
#Override
public Sources deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
List<JsonNode> authors = new ArrayList<>();
JsonToken token;
while ((token = p.currentToken()) != null) {
if (token == JsonToken.FIELD_NAME) {
if ("author".equals(p.getText())) {
authors.add(getPersonObject(p));
}
}
p.nextToken();
}
Sources sources = new Sources();
sources.setAuthors(authors);
return sources;
}
private JsonNode getPersonObject(JsonParser p) throws IOException {
// read start object
p.nextToken();
// read the whole object as node
ObjectNode author = p.readValueAsTree();
// try to evaluate /editor/* path
JsonNode pair = author.at(EDITOR);
if (pair.isMissingNode()) {
// must be bookAuthor
pair = author.at(BOOK_AUTHOR);
}
return pair;
}
}
#JsonDeserialize(using = SourcesJsonDeserializer.class)
class Sources {
private List<JsonNode> authors;
public List<JsonNode> getAuthors() {
return authors;
}
public void setAuthors(List<JsonNode> authors) {
this.authors = authors;
}
#Override
public String toString() {
return authors + "";
}
}
Above code prints:
[{"first":"Peter","last":"Parker"}, {"first":"Karl","last":"S"}]
Well, I had a similar problem myself recently, and found out that the annotation #JsonMerge solves the problem.
I have simplified the XML a little:
<sources>
<author>
<name>Jack</name>
</author>
<source>
<type>Book</type>
</source>
<author>
<name>Jill</name>
</author>
<source>
<type>Journal</type>
</source>
</sources>
With the classes Author and Source
class Author {
String name;
}
class Source {
String type;
}
The Sources class looks as follows:
class Sources {
// We prevent each <author> tag to be wrapped in an <authors> container tag
#JacksonXmlElementWrapper(useWrapping = false)
// Each element is <author> and not <authors> (and we named our field 'authors')
#JacksonXmlProperty(localName = "author")
// This is the property which solves your problem. It causes non-subsequent elements
// with the same name to be merged into the existing list
#JsonMerge
private List<Author> authors;
#JacksonXmlElementWrapper(useWrapping = false)
#JacksonXmlProperty(localName = "source")
#JsonMerge
private List<Source> sources;
}
Related
In my spring boot application, I have below DTO class
#Data
public clsss Feed {
private int id;
private String name;
private String title;
#Builder
#XmlRootElement(name = "feeds")
public static class Feeds {
#XmlElement(name = "feed")
#Singular
private List<Feed> feeds;
}
}
My config class as below
#Component
public class JacksonCustomizer implements Jackson2ObjectMapperBuilderCustomizer {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.modulesToInstall(new JaxbAnnotationModule());
}
}
DAO class implementation as below
public Feeds getAll() {
String sqlQuery = "SELECT * FROM feed WHERE trash = 0";
return Feeds.builder().feeds(namedParameterJdbcTemplate.query(sqlQuery, new BeanPropertyRowMapper<>(Feed.class))).build();
}
Using my ReST API, XML response I am receiving as below:
<feeds>
<feed>
<feed>
<id>1</id>
<name>Val1</name>
<title>Title1</title>
</feed>
<feed>
<id>2</id>
<name>Val2</name>
<title>Title2</title>
</feed>
</feed>
</feeds>
I want to remove <feed> which comes as a wrapper element. Desired output is as below:
<feeds>
<feed>
<id>1</id>
<name>Val1</name>
<title>Title1</title>
</feed>
<feed>
<id>2</id>
<name>Val2</name>
<title>Title2</title>
</feed>
</feeds>
Make changes in the config class to set the default wrapper to false.
#Component
public class JacksonCustomizer implements Jackson2ObjectMapperBuilderCustomizer {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.modulesToInstall(new JaxbAnnotationModule());
jacksonObjectMapperBuilder.defaultUseWrapper(false); //This was missing before
}
}
In src/main/java, I have POJO classes and in src/test/resource I have XML file. I'm writing a Junit test case where I have to parse the XML file into POJOs using JAXBException and then I have to check whether the variables are null or not.
I created a test class and added the asseertNotNull statement but after running the code as "Junit test" I'm getting some errors.
Java Class :-
package com.examples.demo;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlRootElement(name = "book")
// Defining order
#XmlType(propOrder = { "author", "name", "publisher", "isbn" })
public class Book {
private String name;
private String author;
private String publisher;
private String isbn;
// Changing to title
#XmlElement(name = "title")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#XmlAttribute(name = "AuthorName")
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
#XmlElement(name = "publisher")
public String getPublisher() {
return publisher;
}
public void setPublisher(String publisher) {
this.publisher = publisher;
}
#XmlElement(name = "isbn")
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("Book{");
sb.append("name='").append(name).append('\'');
sb.append(", author='").append(author).append('\'');
sb.append(", publisher='").append(publisher).append('\'');
sb.append(", isbn='").append(isbn).append('\'');
sb.append('}');
return sb.toString();
}
}
Test Class :-
package com.examples.demo;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.io.IOException;
import java.io.StringReader;
import javax.xml.bind.JAXBException;
import org.junit.jupiter.api.Test;
import jakarta.xml.bind.JAXBContext;
public class BookTest {
private static final String BOOKSTORE_XML = "src/test/resources/bookstore.xml";
#Test
public void oneDemo() throws JAXBException, IOException, jakarta.xml.bind.JAXBException {
JAXBContext context = JAXBContext.newInstance(BookStore.class);
BookStore bookStore = (BookStore) context.createUnmarshaller().unmarshal(new StringReader(BOOKSTORE_XML));
String name = bookStore.getName();
assertNotNull(name);
}
}
XML file :-
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:bookStore xmlns:ns2="com.zetcode">
<bookList>
<book AuthorName="Neil Strauss">
<title>The Game</title>
<publisher>Harpercollins</publisher>
<isbn>978-0060554736</isbn>
</book>
<book AuthorName="Charlotte Roche">
<title>Feuchtgebiete</title>
<publisher>Dumont Buchverlag</publisher>
<isbn>978-3832180577</isbn>
</book>
</bookList>
<location>Livres belles</location>
<name>Fraport Bookstore</name>
</ns2:bookStore>
How to solve this? please help me
Taking the xml you are using for tests (I omitted non significative tags from code, up to you integrate them):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:bookStore xmlns:ns2="com.zetcode">
<bookList>
<book AuthorName="Neil Strauss">
<title>The Game</title>
<publisher>Harpercollins</publisher>
<isbn>978-0060554736</isbn>
</book>
<book AuthorName="Charlotte Roche">
<title>Feuchtgebiete</title>
<publisher>Dumont Buchverlag</publisher>
<isbn>978-3832180577</isbn>
</book>
</bookList>
<location>Livres belles</location>
<name>Fraport Bookstore</name>
</ns2:bookStore>
The first problem is about the presence of <ns2:bookStore xmlns:ns2="com.zetcode"> tag, you have to define a BookStorewrapper class matching the <bookStore> tag to wrap the <bookList> tag using the XmlRootElement annotation:
#Data //<--lombok annotation
#XmlRootElement(name="bookStore", namespace = "com.zetcode")
#XmlAccessorType(XmlAccessType.FIELD) //<-- used for permit access to fields
public class BookStore {
public BookList bookList;
}
You have to repeat the same process for the bookList tag being aware it contains a list of books with the help of the XmlElement annotation which specifies every book is an element of the list:
#Data
#XmlRootElement(name="bookList")
#XmlAccessorType(XmlAccessType.FIELD)
public class BookList {
#XmlElement(name = "book")
private List<Book> books;
}
#Data
#XmlRootElement(name = "book")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(propOrder = {"author", "name", "publisher", "isbn"})
public class Book {
private String name;
private String author;
private String publisher;
private String isbn;
}
Then you can verify that everything is ok unmarshalling and marshalling the xml, printing it to the stdout :
public class JaxbMain {
public static void main(String[] args) throws JAXBException {
File xml = new File("msg.xml");
//unmarshalling the xml
JAXBContext jc = JAXBContext.newInstance(BookStore.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
BookStore bookStore = (BookStore) unmarshaller.unmarshal(xml);
//marshalling the xml to the stdout
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
//ok it prints the same xml
marshaller.marshal(bookStore, System.out);
}
}
Pom.xml:
<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
I am trying to utilize JAXB to convert xml elements gained from a youtube rss feed into objects. I seem to be following the structure of most examples I have seen, but still cannot get it to work as it always seem the list within feed is empty at all times. Does anyone know how to fix this?
Here are my classes for reference:
feed class:
import java.util.List;
import javax.xml.bind.annotation.*;
import javax.xml.*;
#XmlRootElement(
name = "feed",
namespace = "http://www.w3.org/2005/Atom"
)
#XmlAccessorType (XmlAccessType.FIELD)
public class feed {
#XmlElement(name = "entry")
private List<entry> entries;
public List<entry> getEntry() {
return this.entries;
}
public void setEntry(List<entry> entries) {
this.entries = entries;
}
}
Entry class:
import javax.xml.bind.annotation.*;
import java.util.List;
#XmlRootElement(name = "entry")
#XmlAccessorType(XmlAccessType.FIELD)
public class entry {
private String title, name, id, published;
public void settitle(String title){this.title = title;}
public String gettitle(){return title;}
public void setname(String name){this.name = name;};
public String getname() {
return name;
}
public void setid(String id){this.id = id;}
public String getid() {
return id;
}
public void setpublished(String published){this.published = published;}
public String getpublished() {return published;}
public void PrintVideoInfo(){
System.out.println(gettitle());
System.out.println(getname());
System.out.println(getid());
System.out.println(getpublished());
System.out.println("-----------");
}
}
unmarshalling class:
import java.io.File;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
public class XMLtoObject {
public static void main(String[] args) {
try {
File file = new File("videos.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(feed.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
feed que= (feed) jaxbUnmarshaller.unmarshal(file);
for(entry ent:que.getEntry())
ent.PrintVideoInfo();
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
https://www.youtube.com/feeds/videos.xml?channel_id=UCBcRF18a7Qf58cCRy5xuWwQ this is the xml being used. It is saved as videos.xml and the file path is correct, just not fully included for privacy in the above snippet. Any help would be greatly appreciated.
Please, make your class names follow Pascal notation - it's way easier to read and understand, especially when there's a lot of code. Probably, you should work on your code style more.
Your feed class is annotated correctly, but in order for JAXB to unmarshal XML into entries entry class should also be annotated properly. Also note, that your field names do not match tag names in the xml file provided (<name> is not directly accessible within <feed>).
So, add annotations and classes to match xml file structure.
UPDATE 1
I realized I didn't quite understand xml documents, so I made a little investigation.
If you're completely familiar with xml namespaces, then you can skip this part to Serialization section. Otherwise, read on.
Namespace is a mechanism of dividing xml nodes into non-intersecting sets. Imagine having an <address> tag in your xml, where the tag is defined many times. It could either refer to a web address or a street address, for example, and thus have completely different meaning depending on context. To avoid confusion, you add a namespace prefix like that: <web:address> <street:address> to separate them and group other elements referring to the same namespace. You define them earlier in some root tag:
<root xmlns:web="Web">
<web:address> ... </web:address>
</root>
Also, there's a special namespace - default namespace - which is defined like this: <feed xmlns="Name">. Having a default namespace allows you to omit writing namespace prefix every time you define an xml element.
Let's clear this up with your xml example: it states three namespaces for <feed> element (yt, media, and the default namespace)
<feed xmlns:yt="http://www.youtube.com/xml/schemas/2015"
xmlns:media="http://search.yahoo.com/mrss/"
xmlns="http://www.w3.org/2005/Atom">
That means every element inside <feed> is kind of implicitly prefixed with the default namespace.
Intro to XML namespaces
Atom namespace explained
Serialization
I downloaded the xml file you provided and did some tests. It turned out, that JAXB just "didn't see" the entity tags, since they are hidden behind the default namspace, and we never said to JAXB there is a namespace at all, except for the <feed> element.
So, the solution is to annotate the elements you want to deserialize with a namespace, so that JAXB could understand that.
UPDATE 2
It looked as if the solution provided above was too messy: having to annotate every other element with a namespace is truly a violation of DRY principle. Fortunately, there is a solution to add a default namespace in just one line.
Create a file called package-info.java and add the following in it and replace package with yours:
#XmlSchema(
namespace = "http://www.w3.org/2005/Atom",
elementFormDefault = XmlNsForm.QUALIFIED
)
package package;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
All it does is defines an xml schema for the document we want to parse. You can now remove all namespace = "..." lines and pretty up the code
If you're not familiar with xml schemas, check it out as it's a great way to keep control of xml documents structure.
The code after update 2:
Feed class
import javax.xml.bind.annotation.*;
import java.util.List;
#XmlRootElement(name = "feed")
#XmlAccessorType(XmlAccessType.FIELD)
public class Feed {
#XmlElement(name = "entry")
private List<Entry> entries;
public List<Entry> getEntries() {
return this.entries;
}
}
Entry class
import javax.xml.bind.annotation.*;
import java.util.Date;
#XmlRootElement(name = "entry")
#XmlAccessorType(XmlAccessType.FIELD)
public class Entry {
#XmlElement(name = "title")
private String title;
#XmlElement(name = "id")
private String id;
#XmlElement(name = "published")
private Date datePublished;
#XmlElement(name = "author")
private Author author;
public String toString(){
return String.format("Id: %s, Title: %s, Author: %s, Published: %s",
id,
title,
author.toString(),
datePublished.toString());
}
}
Author class
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "author")
#XmlAccessorType(XmlAccessType.FIELD)
public class Author {
#XmlElement(name = "name")
private String name;
#XmlElement(name = "url")
private String url;
public String getName() {
return name;
}
public String getUrl() {
return url;
}
#Override
public String toString() {
return getName();
}
}
Main
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import java.io.File;
public class Main {
public static void main(String[] args) throws JAXBException {
Feed feed = (Feed) JAXBContext
.newInstance(Feed.class)
.createUnmarshaller()
.unmarshal(new File("youtube_feed.xml"));
for (Entry entry : feed.getEntries()) {
System.out.println(entry.toString());
}
}
}
Reading comprehension
JAXB and XML namespaces
Oracle JAXB annotations reference
I'm trying to convert a Java bean into an xml document and I'm having trouble with some of these more complex interfaces. Here is the setup:
protected Set<Object> field1;
protected Map<Integer, List<Object>> field2;
protected List<String> field3;
protected List<Object> field4;
protected List<Object> field5;
protected List<Object> field6;
protected List<String> field7;
protected List<Object> field8;
In each Object (which is itself a bean) I have the following at the top of each class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"field1",
"field2",
"field3",
"field4",
"field5",
"field6",
"field7",
"field8"
})
#XmlRootElement(name = "root")
I keep getting an exception for the Map of Integers and Lists when I marshal the whole bean. Is there something that I'm missing?
Suppose you have below three class
Customer class
package comparison;
import java.util.ArrayList;
import java.util.List;
public class Customer {
private long id;
private String name;
private Address address;
private List<phonenumber> phoneNumbers;
public Customer() {
phoneNumbers = new ArrayList<PhoneNumber>();
}
}
Address class
package comparison;
public class Address {
private String city;
private String street;
}
and PhoneNumber class
package comparison;
public class PhoneNumber {
private String type;
private String number;
}
Now adding some dummy data
package comparison;
public class Data {
public static Customer CUSTOMER;
static {
CUSTOMER = new Customer();
CUSTOMER.setId(123);
CUSTOMER.setName("Jane Doe");
Address address = new Address();
address.setStreet("1 A Street");
address.setCity("Any Town");
CUSTOMER.setAddress(address);
PhoneNumber workPhoneNumber = new PhoneNumber();
workPhoneNumber.setType("work");
workPhoneNumber.setNumber("555-WORK");
CUSTOMER.getPhoneNumbers().add(workPhoneNumber);
PhoneNumber cellPhoneNumber = new PhoneNumber();
cellPhoneNumber.setType("cell");
cellPhoneNumber.setNumber("555-CELL");
CUSTOMER.getPhoneNumbers().add(cellPhoneNumber);
}
}
So now you apply marshalling to convert the object to xml
package comparison.jaxb;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;
import comparison.Customer;
import static comparison.Data.CUSTOMER;
public class JAXBDemo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
JAXBElement<Customer> jaxbElement = new JAXBElement<Customer>(new QName("customer"), Customer.class, CUSTOMER);
marshaller.marshal(jaxbElement, System.out);
}
}
A JAXBContext needs to be initialized on the binding metadata before
the marshal operation can occur.
Unlike XStream JAXB does not format
the XML by default, so we will enable this feature.
With no metadata
specified we need to supply JAXB with a root element name (and
namespace).
The code will produce result:
<customer>
<id>123</id>
<address>
<city>Any Town</city>
<street>1 A Street</street>
</address>
<name>Jane Doe</name>
<phoneNumbers>
<number>555-WORK</number>
<type>work</type>
</phoneNumbers>
<phoneNumbers>
<number>555-CELL</number>
<type>cell</type>
</phoneNumbers>
By default JAXB will access public fields and properties. You can configure JAXB to use field access with the following package level annotation.
#XmlAccessorType(XmlAccessType.FIELD)
package comparison;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
You can look at this blogpost for details.
I went through almost all questions related to this topic here. But was not able to get a proper solution.
My issue is as follows:
I created a simple program to unmarshall an xml file for which i had a xsd. I was able to do that successfully. But if i am getting an xml without xsd, how can I get my attributes from that, if the xml looks something like this :
<items>
<item>
<code>12000</code>
<name>Samsung 620</name>
<price>9999</price>
</item>
<item>
<code>15000</code>
<name>NOKIA</name>
<price>19999</price>
</item>
<item>
<code>18000</code>
<name>HTC 620</name>
<price>29999</price>
</item>
</items>
Here I don't have an xsd to generate my classes. How can i proceed? Kindly help me.
Thank You
Below is one way that you could map your use case with a JAXB (JSR-222) implementation:
Items
We will use the following class for the root object and annotate it with #XmlRootElement. The #XmlRootElement annotation tells JAXB that this class should be instantiated if the root element in the document being unmarshalled is items, you can also specify a different name #XmlRootElement(name="foo").
package forum11152046;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Items {
private List<Item> items;
#XmlElement(name="item")
public List<Item> getItems() {
return items;
}
public void setItems(List<Item> items) {
this.items = items;
}
}
Item
In this example I created a class where all the property names correspond directly to the names in the XML document. This means there aren't any annotations that need to be added. If you need to override the default name you can use an annotation such as #XmlElement to do so. I used the #XmlElement annotation to do this in the Items class for the items property.
package forum11152046;
public class Item {
private int code;
private String name;
private int price;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
Demo
package forum11152046;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Items.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum11152046/input.xml");
Items items = (Items) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(items, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<items>
<item>
<code>12000</code>
<name>Samsung 620</name>
<price>9999</price>
</item>
<item>
<code>15000</code>
<name>NOKIA</name>
<price>19999</price>
</item>
<item>
<code>18000</code>
<name>HTC 620</name>
<price>29999</price>
</item>
</items>
If you want to stick with JAXB, you can either write an XML Schema Document on your own to validate such XML (it looks simple but it's just an instance, you need to find out what could change in these documente beforehand) or create a POJO with JAXB annotations matching these nodes. I'm afraid there's no other way. You still have to know well what the format allows.