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.
Related
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;
}
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
Im trying to marshall an XML into a class heiarchy, using JAXB - and i want the heiarchy to use inheritence in a generic way. I will explain better:
I have the following XML:
<Country name="USA">
<City name="NewYork">
<Street name="Something"/>
<Street name="Something2"/>
</City>
<City name="LosAngeles">
<Street name="Something"/>
<Street name="Something2"/>
</City>
<Country>
<Country .....>
<.....>
<.....
</Country>
etc. where eatch country can have multiple cities and each city can have multiple streets.
I want to create a class called GeneralLocation which will look something like this:
#XmlTransient
public abstract class GeneralLocation {
private String name;
protected List<GeneralLocation> sons;
#XmlAttribute(name="name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<GeneralLocation> getSons(){
return this.sons
}
public void setSons(List<GeneralLocation> sons){
this.sons = sons;
}
}
then create the Country,City, and Street classes to inherite from the GeneralLocation and over ride the getSons method with the correct name for JAXB parsing.
public class Country extends GeneralLocation{
#XmlElement(name="City")
public List<GeneralLocation> getSons(){
return this.sons
}
public void setSons(List<GeneralLocation> sons){
this.sons = sons;
}
}
I have tried all sorts of variations on this basic code, none of them do the work. i'm not adding any specific one i did because they all throw verious Exceptions that seems to indicate i am really not on the right path, so i decided to add this basic skeleton and hope for any pointers from you guys...
Can anyone help me with this?
Thanks
You can use the #XmlElementRef annotation for your use case.
Java Model
GeneralLocation
You can use the #XmlElementRef annotation on the sons property. The name of the element in the XML will be based on the root element associated with the subclass of the object referenced.
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlSeeAlso({Country.class, City.class, Street.class})
public abstract class GeneralLocation {
private String name;
protected List<GeneralLocation> sons;
#XmlAttribute(name="name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#XmlElementRef
public List<GeneralLocation> getSons(){
return this.sons;
}
public void setSons(List<GeneralLocation> sons){
this.sons = sons;
}
}
Country
The subclasses don't need to override the sons property. The only thing they need to have is the #XmlRootElement annotation that will be used by the #XmlElementRef annotation on GeneralLocation to derive the element name.
import javax.xml.bind.annotation.*;
#XmlRootElement(name="Country")
public class Country extends GeneralLocation {
}
City
import javax.xml.bind.annotation.*;
#XmlRootElement(name="City")
public class City extends GeneralLocation {
}
Street
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="Street")
public class Street extends GeneralLocation {
}
Demo Code
Below is some example code that unmarshals the XML to objects, and then marshals it back to XML again. Note that since we used the #XmlSeeAlso annotation on the GeneralLocation class we don't need to specify all the subclasses when we bootstrap the JAXBContext.
Demo
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(GeneralLocation.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("input.xml");
GeneralLocation result = (GeneralLocation) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(result, System.out);
}
}
input.xml/Output
The XML in your question wasn't valid since you didn't have just one root element, so for my answer I chose to use just a single country.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Country name="USA">
<City name="NewYork">
<Street name="Something"/>
<Street name="Something2"/>
</City>
<City name="LosAngeles">
<Street name="Something"/>
<Street name="Something2"/>
</City>
</Country>
My solution is not as generic as you would like but I figured it might help you understand why you received various Exception (I myself stumbled upon many trying to find a solution).
First I used the following XML as test (I assume you omitted to root element):
<root>
<Country name="USA">
<City name="NewYork">
<Street name="Something" />
<Street name="Something2" />
</City>
<City name="LosAngeles">
<Street name="Something" />
<Street name="Something2" />
</City>
</Country>
<Country name="France">
<City name="Paris">
<Street name="Champs-Elysees" />
<Street name="La sante" />
</City>
</Country>
</root>
As for the classes themselves, I kept the same structure as yours, Country, City and Street inherits from GeneralLocation. However, I removed the list of children from GeneralLocation and instead each class (except Street) holds a List with the proper children type (City inside Country for instance).
It seems to fit best your goals since your original structure allow Street to have City or Country as children, which aside of the natural contradiction could cause issue if you want to marshall from a class model to XML. Therefore I ended with the following classes:
public abstract class GeneralLocation {
protected String name;
public GeneralLocation() {
name = "";
}
#XmlAttribute(name = "name")
public String getName() {
return name;
}
}
I got rid of the #XmlTransient annotation since it is not necessary in my experience.
#XmlType
public class Street extends GeneralLocation {
public Street() {
}
}
Street has no children and thus is prevented from holding cities or countries as children. I also just realized you didn't the #XmlType annotation on top of the class definition, this might a cause of parsing error here.
#XmlType
public class City extends GeneralLocation {
private List<Street> streets;
public City() {
streets = new LinkedList<Street>();
}
#XmlElement(name = "Street")
public List<Street> getStreets() {
return streets;
}
}
#XmlType
public class Country extends GeneralLocation{
private List<City> cities;
public Country() {
cities = new LinkedList<City>();
}
#XmlElement(name="City")
public List<City> getCities() {
return cities;
}
}
#XmlRootElement(namespace = "", name = "root")
public class Root {
private List<Country> countries;
public Root() {
countries = new LinkedList<Country>();
}
#XmlElement(name = "Country")
public List<Country> getCountries() {
return countries;
}
}
City and Country are really similar here, they both hold a list of their respective children (the user can't mess up = security) and JAXB can access the name attribute in the superclass through the annotation (this however depends on your access policy, see below). Finally I put a root class that is tagged with the #XmlRootElement and holds the countries.
Finally, I would recommend that you define the accessor type in your package definition (file package-info.java) to make sure the way JAXB access the data is consistent (only annotations for instance):
#XmlAccessorType(XmlAccessType.NONE) //annotations only
package test;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
With that you should be all set to unmarshall your xml :) As a bonus, the main I used:
public static void main(String[] args) throws JAXBException {
JAXBContext jaxbc = JAXBContext.newInstance(Root.class);
Unmarshaller unm = jaxbc.createUnmarshaller();
File countries = new File("countries.xml");
Root result = (Root) unm.unmarshal(countries);
}
Sadly I couldn't get JAXB to run with the children held by GeneralLocation, I get an UnmarshalException (could not create an instance of GeneralLocation) but I hope this helps anyway. I thinks this a more appropriate way to approach the problem though, it is not as flexible but ensure that the document's structure is consistent.
Also you should check out Blaise Doughan blogs : http://blog.bdoughan.com/ , it contains a lot of useful informations for JAXB developers ;)
I like to suggest you a different approach, because I think you use the wrong tool for your problem. When you use data projection (Disclosure: I'm affiliated with this project) instead of data binding, you get a short and general solution that is IMHO not possible with JAXB:
public class LocationExample {
public interface GeneralLocation {
#XBRead("#name")
String getName();
#XBRead("name()")
String getType();
#XBRead("./*")
List<GeneralLocation> getSons();
#XBRead("/root/Country")
List<GeneralLocation> getCountries();
}
public static void main(String... args) throws IOException {
GeneralLocation location = new XBProjector().io().url("resource://locations.xml").read(GeneralLocation.class);
printLocations(location.getCountries());
}
public static void printLocations(List<GeneralLocation> locations) {
for (GeneralLocation son:locations) {
System.out.println(son.getType()+": "+son.getName());
printLocations(son.getSons());
}
}
}
I needed to add a root element to your xml to make it valid. For the input
<root>
<Country name="USA">
<City name="NewYork">
<Street name="Something" />
<Street name="Something2" />
</City>
<City name="LosAngeles">
<Street name="Something" />
<Street name="Something2" />
</City>
</Country>
</root>
The program prints out:
Country: USA
City: NewYork
Street: Something
Street: Something2
City: LosAngeles
Street: Something
Street: Something2
I know this is not the JAXB solution you were asking for, but it's shorter and it works.
I need to marshall and unmarshall a Java class to XML. The class in not owned by me, that I cannot add anotations so that I can use JAXB.
Is there a good way to convert the Java to XML with the given contraint?
Also, thought a tool may be helpful, but I would be more intersted it there is some Java API to do the same.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
DOMAIN MODEL
I will use the following domain model for this answer. Note how there are no JAXB annotations on the model.
Customer
package forum11693552;
import java.util.*;
public class Customer {
private String firstName;
private String lastName;
private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public List<PhoneNumber> getPhoneNumbers() {
return phoneNumbers;
}
public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
this.phoneNumbers = phoneNumbers;
}
}
PhoneNumber
package forum11693552;
public class PhoneNumber {
private String type;
private String number;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
OPTION #1 - Any JAXB (JSR-222) Implementation
JAXB is configurartion by exception, this means you only need to add annotations where you want the mapping behaviour to differ from the default. Below is a link to an example demonstrating how to use any JAXB impl without annotations:
Demo
package forum11693552;
import javax.xml.bind.*;
import javax.xml.namespace.QName;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Customer customer = new Customer();
customer.setFirstName("Jane");
customer.setLastName("Doe");
PhoneNumber workPhone = new PhoneNumber();
workPhone.setType("work");
workPhone.setNumber("555-1111");
customer.getPhoneNumbers().add(workPhone);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
JAXBElement<Customer> rootElement = new JAXBElement<Customer>(new QName("customer"), Customer.class, customer);
marshaller.marshal(rootElement, System.out);
}
}
Output
<customer>
<firstName>Jane</firstName>
<lastName>Doe</lastName>
<phoneNumbers>
<number>555-1111</number>
<type>work</type>
</phoneNumbers>
</customer>
For More Information
http://wiki.eclipse.org/EclipseLink/Examples/MOXy/GettingStarted/TheBasics
OPTION #2 - EclipseLink JAXB (MOXy)'s External Mapping Document
If you do want to customize the mappings, then you may be interested in MOXy's external mapping document extension. A sample mapping document looks like the following:
oxm.xml
<?xml version="1.0"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="forum11693552">
<java-types>
<java-type name="Customer">
<xml-root-element />
<java-attributes>
<xml-element java-attribute="firstName" name="first-name" />
<xml-element java-attribute="lastName" name="last-name" />
<xml-element java-attribute="phoneNumbers" name="phone-number" />
</java-attributes>
</java-type>
<java-type name="PhoneNumber">
<java-attributes>
<xml-attribute java-attribute="type" />
<xml-value java-attribute="number" />
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
jaxb.properties
To enable MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html):
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
When using EclipseLink MOXy as your JAXB provider (see), you can leverage the external mapping document when you bootstrap your JAXBContext
package forum11693552;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.namespace.QName;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String,Object>(1);
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum11693552/oxm.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class}, properties);
Customer customer = new Customer();
customer.setFirstName("Jane");
customer.setLastName("Doe");
PhoneNumber workPhone = new PhoneNumber();
workPhone.setType("work");
workPhone.setNumber("555-1111");
customer.getPhoneNumbers().add(workPhone);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
JAXBElement<Customer> rootElement = new JAXBElement<Customer>(new QName("customer"), Customer.class, customer);
marshaller.marshal(rootElement, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<customer>
<first-name>Jane</first-name>
<last-name>Doe</last-name>
<phone-number type="work">555-1111</phone-number>
</customer>
For More Information
http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html
http://blog.bdoughan.com/2012/04/extending-jaxb-representing-metadata-as.html
Have you looked at XStream ? It will deserialise/deserialise a standard POJO without annotations or XSDs. You can provide customisations to affect how elements appear in the XML and pretty much works out-of-the-box.
You could write a custom XmlAdapter and annotate fields of the constrained type with a XmlJavaTypeAdapter annotation. The basics would be something like this:
public enum CannotBeAnnotated { value1, value2; }
#XmlRootElement(name="client")
public class ClientClass {
#XmlJavaTypeAdapter(Bridge.class)
public CannotBeAnnotated;
}
#XmlRootElement(name="representation")
public class XmlType {
#XmlValue
public String value;
}
public class Bridge extends XmlAdapter<XmlType, CannotBeAnnotated>{
public XmlType marshal(CannotBeAnnotated c) {
XmlType x=new XmlType();
x.value=c.name();
return x;
}
public CannotBeAnnotated unmarshall(XmlType x) {
return CannotBeAnnotated.valueOf(x.value);
}
}
Of course for enums this would not be useful as JAXB knows how to deal with them. I just picked an enum for simplicity so you can see the idea:
Design an XML representation that you do control
Write an adapter converting that Java type into the desired type
Annotate "client" code referencing the adapter for the desired type
Profit.
I am using JAXB to parse some xml.
<countries>
<Name language="en">Australia</Name>
<Name language="se">Australien</Name>
</countries>
If I in my class Countries use
#XmlElement(name = "Name", required = true)
protected List<Name> name;
everything works.
However I would like to only get the attribute where language="en"
So I in my Countries class have
protected String name
not a collection.
Is there a good way to solve this with some annotation for example?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group
Below are two ways you could handle this use case. The first is a little more code but could be done with any JAXB implementation. The second is less code, but requires you use EclipseLink JAXB (MOXy).
OPTION #1 - ANY JAXB (JSR-222) IMPLEMENTATION
Demo
You could use a filtered stream reader to filter out the unwanted elements and have your JAXB implementation unmarshal that.
package forum11586106;
import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.transform.stream.StreamSource;
public class Demo {
private static final String LANGUAGE_CODE = "en";
public static void main(String[] args) throws Exception {
XMLInputFactory xif = XMLInputFactory.newFactory();
XMLStreamReader xsr = xif.createXMLStreamReader(new StreamSource("src/forum11586106/input.xml"));
xsr = xif.createFilteredReader(xsr, new StreamFilter() {
private boolean isReading = true;
#Override
public boolean accept(XMLStreamReader reader) {
if(reader.isStartElement() && "Name".equals(reader.getLocalName())) {
isReading = LANGUAGE_CODE.equals(reader.getAttributeValue("", "language"));
return isReading;
} else if(reader.isEndElement() && !isReading) {
isReading = true;
return false;
}
return true;
}});
JAXBContext jc = JAXBContext.newInstance(Countries.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Countries countries = (Countries) unmarshaller.unmarshal(xsr);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(countries, System.out);
}
}
Countries
package forum11586106;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Countries {
private String name;
#XmlElement(name="Name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
input.xml
With this approach the language attribute is not included in the output:
<countries>
<Name language="en">Australia</Name>
<Name language="se">Australien</Name>
</countries>
Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<countries>
<Name>Australia</Name>
</countries>
OPTION #2 - ECLIPSELINK JAXB (MOXy)
We will leverage MOXy's #XmlPath extension to map to the Name element that has a language attribute with value en (see http://blog.bdoughan.com/2011/03/map-to-element-based-on-attribute-value.html).
Countries
package forum11586106;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement
public class Countries {
private String name;
#XmlPath("Name[#language='en']/text()")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
jaxb.properties
To use MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
With this approach the element filtering is handled by the #XmlPath mapping, so the runtime portion becomes much simpler. Note how only the standard JAXB runtime APIs are used.
package forum11586106;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Countries.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum11586106/input.xml");
Countries countries = (Countries) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(countries, System.out);
}
}
input.xml
<countries>
<Name language="en">Australia</Name>
<Name language="se">Australien</Name>
</countries>
Output
With this approach the language attribute is included in the output:
<?xml version="1.0" encoding="UTF-8"?>
<countries>
<Name language="en">Australia</Name>
</countries>