Please take a look at the following example. I got the exception that XmlIDREF annotation is not allowed in class Bar. If you use the concrete Class Bar instead of IBar it works perfectly. But i have to use the interface. Is there a solution for this problem?
#XmlRootElement
public class Foo {
#XmlElement(name = "bar")
public List<Bar> bars;
public String fooProp;
}
public interface IBar {
#XmlID
String getId();
void setId(String id);
}
#XmlRootElement
public class Bar implements IBar {
#XmlIDREF
#XmlAnyElement
public IBar bar;
public String barProp;
private String id;
#Override
#XmlID
public String getId() {
return this.id;
}
#Override
public void setId(String id) {
this.id = id;
}
}
public static void main(String[] args) throws Exception {
File file = null;
file = new File("somewhere");
try(Writer w = new FileWriter(file)){
JAXBContext context = JAXBContext.newInstance("jaxbtest");
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
Foo foo = new Foo();
foo.fooProp ="FooProperty";
Bar bar1 = new Bar();
bar1.barProp = "BarProperty1";
bar1.setId("1");
Bar bar2 = new Bar();
bar2.barProp = "BarProperty2";
bar2.setId("2");
bar1.bar = bar2;
bar2.bar = bar1;
List<Bar> list = new ArrayList<Bar>();
list.add(bar1);
list.add(bar2);
foo.bars = list;
m.marshal(foo, w);
} catch (Exception e) {
e.printStackTrace();
}
}
A workaround is to use #XmlElements annotation and tell the jaxb context all the possible implementations of IBar:
#XmlIDREF
#XmlElements( #XmlElement( type = Bar.class )/*, #XmlElement( type = AnotherBar.class) */ )
public IBar bar;
Related
i have a xml and i want to save into a string the sub xml formed by the child of a specific tag.
this is a xml example:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<SampleDTO>
<id>1</id>
<someList>
<someObject>
<amount>32</amount>
<id>1</id>
<someDescription>I am a description</someDescription>
</someObject>
<someObject>
<amount>66</amount>
<id>2</id>
<someDescription>I am another description</someDescription>
</someObject>
<someObject>
<amount>78</amount>
<id>13</id>
<someDescription>Guess what? I am a description</someDescription>
</someObject>
</someList>
<otherList>
<otherObject>
<flag>true</flag>
<id>1</id>
<otherDescription>Oh nice, a description</otherDescription>
</otherObject>
</otherList>
</SampleDTO>
i want , passing for example "someList" , to save into a String the sub-xml element and value, because next i deserialize it into a java object
use the JAXB unmarshaller for converting xml document into java objects.
firstly add JAXB dependency into your project's classpath. for more info
SampleDTO.java
#XmlRootElement
public class SampleDTO {
private String id;
private List<SomeList> someList;
private List<OtherList> otherList;
#XmlElement
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#XmlElement
public List<SomeList> getSomeList() {
return someList;
}
public void setSomeList(List<SomeList> someList) {
this.someList = someList;
}
#XmlElement
public List<OtherList> getOtherList() {
return otherList;
}
public void setOtherList(List<OtherList> otherList) {
this.otherList = otherList;
}
}
SomeList.java
#XmlRootElement
public class SomeList {
private List<SomeObject> someObject;
#XmlElement
public List<SomeObject> getSomeObject() {
return someObject;
}
public void setSomeObject(List<SomeObject> someObject) {
this.someObject = someObject;
}
}
OtherList.java
#XmlRootElement
public class OtherList {
private List<OtherObject> otherObject;
#XmlElement
public List<OtherObject> getOtherObject() {
return otherObject;
}
public void setOtherObject(List<OtherObject> otherObject) {
this.otherObject = otherObject;
}
}
SomeObject.java
#XmlRootElement
public class SomeObject {
private String amount;
private String id;
private String someDescription;
#XmlElement
public String getAmount() {
return amount;
}
public void setAmount(String amount) {
this.amount = amount;
}
#XmlElement
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#XmlElement
public String getSomeDescription() {
return someDescription;
}
public void setSomeDescription(String someDescription) {
this.someDescription = someDescription;
}
}
OtherObject.java
#XmlRootElement
public class OtherObject {
private String flag;
private String id;
private String otherDescription;
#XmlElement
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
#XmlElement
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#XmlElement
public String getOtherDescription() {
return otherDescription;
}
public void setOtherDescription(String otherDescription) {
this.otherDescription = otherDescription;
}
}
Unmarshalling with JAXB
public class Main {
public static void main(String[] args) {
try {
File file = new File("file.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(SampleDTO.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
SampleDTO sampleDTO= (SampleDTO) jaxbUnmarshaller.unmarshal(file);
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
Your java class/object should have at least these 3 instance vars :
private int amount
private int id
private String description
Then use some xml parsing library (eg jdom2), and for each <someObject> tag you iterate through, initialize a new object of your class and assign to it the values parsed from the xml (amount / id / description) , and add each newly created object in a List or array etc..
There are many open-source XML processing packages available.
I like Jackson.
Here is a link to a Baeldung Article about Jackson XML
The summary is this:
Add a Jackson dependency to your POM.
Create an object structure that represents your xml structure.
Create an XmlMapper.
Use the XmlMapper.
I'm trying to use JAXB to unmarshal a data (Polygon) with two #XmlJavaTypeAdapter annotations (Subscriber, Tag) to and from an XML file. Each object has it's own XML file, and the objects are related thus:
Each Subscriber may hold multiple Polygons
Each Polygon may hold multiple Subscribers
Each Polygon may hold multiple Tags
I'm trying to unmarshall the Polygon XML file back to its class object, with its Subscribers and Tags in tact, but can't work out how to do it.
== Subscriber.java ==
#XmlRootElement(name = "Subscriber")
#XmlAccessorType(XmlAccessType.FIELD)
public class Subscriber {
private String ID;
private Set<Polygon> polygons;
#XmlAttribute(name = "ID")
public String getID() {
return this.ID;
}
public void setID(String ID) {
this.ID = ID;
}
#XmlJavaAdapter(PolygonXmlAdapter.class)
#XmlElementWrapper(name = "Polygons")
#XmlElement(name = "Polygon")
public set<Polygon> getPolygons() {
return this.polygons;
}
public void setPolygons(Set<Polygon> polygons) {
this.polygons = polygons;
}
}
== Polygon.java ==
#XmlRootElement(name = "{Polygon}")
#XmlAccessorType(XmlAccessType.FIELD)
public class Polygon {
private String ID;
private Set<Subscriber> subscribers;
private Set<Tag> tags
#XmlAttribute(name = "ID")
public String getID() {
return this.ID;
}
public void setID(String ID) {
this.ID = ID;
}
#XmlJavaAdapter(SubscriberXmlAdapter.class)
#XmlElementWrapper(name = "Subscribers")
#XmlElement(Subscriber)
public Set<Subscriber> getSubscribers() {
return this.subscribers;
}
#XmlJavaAdapter(TagXmlAdapter.class)
#XmlElementWrapper(name = "Tags")
#XmlElement(name = "Tag")
public Set<Tag> getTags() {
return this.tags
}
public void setTags(Set<Tag> tags) {
this.tags = tags;
}
}
== Tag.java ==
#XmlRootElement(name = "Tag")
#XmlAccessorType(XmlAccessType.FIELD)
public class Tag {
private String ID;
#XmlAttribute(name = "ID")
public String getID() {
return this.ID;
}
public void setID(String ID) {
this.ID = ID;
}
}
== SubscriberXmlAdapter.java ==
public class SubscriberXmlAdapter extends XmlAdapter<String, Subscriber> {
private Map<String, Subscriber> subscribers = new HashMap<>();
private Map<String, Subscriber> getSubscribers() {
return subscribers;
}
#Override
public Subscriber unmarshal(String v) throws Exception {
return subscribers.get(v);
}
#Override
public String marshal(Subscriber v) throws Exception {
return v.getID();
}
}
== TagXmlAdapter.java ==
public class TagXmlAdapter extends XmlAdapter<String, Tag> {
private Map<String, Tag> tags = new HashMap<>();
private Map<String, Tag> getTags() {
return tags;
}
#Override
public String unmarshal(String v) throws Exception {
return tags.get(v);
}
#Override
public String marshal(Tag v) throws Exception {
reutrn v.getID();
}
}
== SubscriberCollection.java ==
#XmlRootElement(name = "Subscribers")
#XmlAccessorType(XmlAccessType.FIELD)
public class SubscriberCollection {
private Collection<Subscriber> collection;
#XmlElement(name = "Subscriber")
public Collection<Subscriber> getSubscribers() {
return this.collection;
}
public void setCollection(Collection<Subscriber> collection) {
this.collection = collection;
}
}
== TagCollection.java ==
#XmlRootElement(name = "Tags")
#XmlAccessorType(XmlAccessType.FIELD)
public class TagCollection {
private Collection<Subscriber> collection;
#XmlElement(name="Tag")
public Collection<Tag> getSubscribers() {
return this.collection;
}
public void setCollection(Collection<Subscriber> collection) {
this.collection = collection;
}
}
And to execute the unmarshalling, three xml files - for subscribers, polygons, and tags - containing their respective data are used:
JAXBContext jaxbContext = JAXBContext.newInstance(SubscriberCollection.class, PolygonTagCollection.class, Polygon.class);
unmarshaller subUnmarshaller = jaxbContext.createunmarshaller();
SubscriberCollection subCollection = (SubscriberCollection) subUnmarshaller.unmarshal(new ByteArrayInputStream(subscribersXml.getBytes(StandardCharset.UTF_8.name())));
Unmarshaller tagUnmarshaller = jaxbContext.createUnmarshaller();
TagCollection tagCollectino = (TagCollection) tagUnmarshaller.un,arshal(new ByteArrayInputStream(tagXml.getBytes(StandardCharsets.UTF_8.name())));
Unmarshaller polUnmarshaller = jaxbContext.createUnmarshaller();
SubscriberXmlAdapter subAdapter = new SubscriberXmlAdapter();
for (Subscriber sub : subscriberCollection.getCollection()) {
subAdapter.getCollection().put(sub.getID(), sub);
}
TagXmlAdapter tagAdapter = new TagXmlAdapter();
for (Tag tag : tagCollection.getCollection()) {
tagAdapter.getCollection().put(tag.getID(), tag);
}
polUnmarshaller.setAdapter(SubscriberXmlAdapter.class subAdapter);
polUnmarshaller.unmarshal(new ByteArrayInputStream(polygonXml.getBytes(StandardCharsets.UTF_8.name())));
This unmarshalls the Polygon with the correct subscribers, but not with the correct tags.
I can only set one XmlAdapter to the unmarshaller, so it can only populate the subscribers or the tags within the polygon being unmarshalled.
Is there a way to combine these adapters? Or is there a better way of doing things altogether.
Thanks!
I have problems with persisting the following construct with JPA.
#MappedSuperclass
public abstract class SuperFoo {
#Embedded
private Bar bar;
public SuperFoo() {}
public SuperFoo(...) {
...
this.bar = new Bar("barKey");
}
...
public void setBar(Bar bar) {
this.bar = bar;
}
public Bar getBar() {
return bar;
}
}
#Embeddable
public class Bar {
#ElementCollection
Map<String, Long> durations;
#ElementCollection
Map<String, Integer> integers;
public Bar() {}
public Bar(String barKey) {
his.durations = new HashMap<String, Long>();
this.integers = new HashMap<String, Integer>();
durations.put(key, new Long(15));
integers.put(key, new Integer(10));
}
... setters and getters.....
}
#Entity
public class Foo extends SuperFoo {
#Id
private String id;
public Foo() {}
public Foo(String id, *superArguments*) {
super(*superArguments*);
this.id = id;
}
public String getId() { return id; }
public void setId(String id) {
this.id = id;
}
}
#Entity
public class FooContainer {
#Id
String id;
#OneToMany(cascade = CascadeType.ALL)
List<Foo> fooList;
public FooContainer() {}
public FooContainer(String id) {
this.id = id;
this.fooList = new ArrayList<>();
}
public void addFoo(Foo foo) {
this.fooList.add(foo);
}
public void setFooList(List<Foo> fooList) {this.fooList = fooList;}
public List<Foo> getFooList() { return fooList; }
}
Method where persisting takes place:
public void method() {
FooContainer fooContainer = fooContainerRepository.getOne(...);
fooContainer.addFoo(new Foo(...));
fooContainerRepository.save(fooContainer);
}
JPA creates the following tables (looks fine):
foo
foo_durations
foo_integers
Now, when I save an instance of Foo, everything gets persisted (fields in Foo). BUT I get no entries in foo_durations and foo_integers. Furthermore, I get no exceptions.
I did a little research myself and found the following:
"An embeddable class that is contained within an element collection must not contain an element collection."
I think, here, that is not the case because my embeddable class "Bar" is not contained within an element collection. So does anybody know, what I might have done wrong?
Thanks in advance!
Update:
I forgot to mention that the Bar Object inside SuperFoo is not set via constructor but calculated inside the constructor.
interface Foo
public String key()
class Bar implements Foo
public int id;
public String name;
public Bar2 bar2; <--- bar2.key() should be used as json value
String key() { return name }
class Bar2 implements Foo
public int id;
public int name;
public Bar bar; <--- bar.key() should be used as json value
String key() { return name }
Whenever any object of type Foo is referenced in serialization, it's value should be object.key().
For deserialization, the value to should be used to lookup the actual object (Bar, Bar2, etc)
How can this be done with Jackson?
you need a getter method for the common property. Change Foo into abstract class and define the property and getter method there.
public abstract class Foo implements Serializable{
public String name;
public Foo bar;
public Foo() {
}
public String getBar(){
return bar.name;
}
public void setBar(Foo bar) {
this.bar = bar;
}
}
class Bar extends Foo{
public int id;
public Bar() {
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Bar bar = new Bar();
Bar2 bar2 = new Bar2();
bar.id = 1; bar.name = "bar1";bar.setBar(bar2);
bar2.id = 2; bar2.name = "bar2"; bar2.setBar(bar);
System.out.println(mapper.writeValueAsString(bar));
}
}
public class Bar2 extends Foo {
public int id;
public Bar2() {
}
}
I use different NoSQL databases and depending on the database I need to name the "id" different. So for example in OrientDB the id is named "#rid"
#JsonProperty("#rid")
private String id;
And for MongoDB the id is named "_id"
#JsonProperty("#_id")
private String id;
I do not know what is wrong with the modern DB developers not just naming the id field "id" ^^. But now I have a problem. How can I dynamically serialize/deserialize the id field in some case as "#rid" and in another case as "_id"?
EDIT:
Based on rmullers suggestion I have tried to use mixins. So I have for example:
public interface IdMixins {
}
public interface MongoIdMixIn extends IdMixins {
#JsonProperty("_id") String getId();
#JsonProperty("_id") void setId(String id);
}
public interface OrientIdMixIn extends IdMixins{
#JsonProperty("#rid") String getId();
#JsonProperty("#rid") void setId(String id);
}
Where IdMixins is a completly empty interface just used to get more controll which interfaces can be passet to the mapper.
Then there is a class:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#javaClass")
public abstract class AbstractBean implements Serializable {
private static final long serialVersionUID = -1286900676713424199L;
// #JsonProperty("#rid")
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
But when I run this simple test, the output is still "id":
public class MixinTest {
public static void main(String[] args) throws JsonProcessingException {
Foo f = new Foo();
f.setId("123");
f.setBar("lala");
ObjectMapper mapper = new ObjectMapper();
ObjectMapper m2 = mapper.copy();
m2.addMixInAnnotations(AbstractBean.class, MongoIdMixIn.class);
System.out.println(m2.writeValueAsString(f));
ObjectMapper m3 = mapper.copy();
m3.addMixInAnnotations(AbstractBean.class, OrientIdMixIn.class);
System.out.println(m3.writeValueAsString(f));
}
public static class Foo extends AbstractBean {
private String bar;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
}
Outputs:
{"#javaClass":"test.MixinTest$Foo","id":"123","bar":"lala","#class":"Foo"}
{"#javaClass":"test.MixinTest$Foo","id":"123","bar":"lala","#class":"Foo"}
Have you tried using http://wiki.fasterxml.com/JacksonMixInAnnotations? Then you can use an OrientDbMixin and a MongoDbMixin with different #JsonProperty configuration.
Update: Working example
public final class JacksonTest {
static final class ExampleBean {
private String id;
private String bar;
#JsonProperty("donotwanttoseethis")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
public interface MongoIdMixIn {
#JsonProperty("_id") String getId();
}
public interface OrientIdMixIn {
#JsonProperty("#rid") String getId();
}
private final static Logger LOG = LoggerFactory.getLogger();
public static void main(String[] args) throws JsonProcessingException {
ExampleBean bean = new ExampleBean();
bean.setId("1234");
bean.setBar("lala");
ObjectMapper m2 = new ObjectMapper();
m2.addMixInAnnotations(ExampleBean.class, MongoIdMixIn.class);
LOG.info(m2.writeValueAsString(bean));
ObjectMapper m3 = new ObjectMapper();
m3.addMixInAnnotations(ExampleBean.class, OrientIdMixIn.class);
LOG.info(m3.writeValueAsString(bean));
}
}