Unmarshalling multiple XML elements to one field using JAXB - java

I have an XML file in format described below. I'm currently using JAXB to unmarshall the XML elements (event) to Java objects (Event). All properties work fine, except one...
As you can see in the XML, the date element stores only the date and the time stores only the time of the event. I would like to combine these two XML elements into one LocalDataTime field named time, with appropriate getters and setters.
XML:
...
<event>
...
<date>2014-02-19</date>
<time>2000-01-01T14:17:00Z</time>
...
</event>
...
Desired Java Object:
public class Event {
...
// Returns a combination of the date and time stored in the XML
public LocalDateTime getDateTime() {
return dateTime;
}
...
}
My first thought was to use a XMLAdapter but it seems that this only allows me to map 1 XML element to 1 Java object.
After that I tried to implement this by overriding the setters setTime and setDate. These setters would each just change the time or date of the stored time.. But I wasn't able to get it working and it also seemed a quite ugly way to do this.
I already read the following JAXB convert multiple attributes to one type while unmarshalling. But for this project, I would rather not use an external library like MOXy.
Does anyone know how to do this using solely JAXB in a clean way?

You could define in your Event object lifecycle methods:
void afterUnmarshal(Unmarshaller unm, Object parent) {
localDateTime = ....
};
boolean beforeMarshal(Marshaller mar) {
date = localDateTime.toDate();
....
};
to construct the LocalDateTime property after unmarshalling (using the date and time values), and prepare date/time fields before marshalling using the current LocaLDateTime value.
You would still need the time/date fields to match the xml and the localDateTime field must be XmlTransient. So it is not so different from the set/getter approach but probably more "clean".

Related

Custom JSON Parser for Dates in Typescript in different classes

I have an Angular WebApp who retrives data from a backend Java server via http get.
Example:
public getStateForUser(): Observable<UserRegistrationState> {
return this.http.get<any>(Environment.getServerAddress() + '/' +
UserRegistrationStateService.SERVICE_URL + '/getStateForUser', {
params: {
sessionIdentityStr: this.authGuard.getSession().sessionIdentityStr
}
});
}
export class UserRegistrationState {
registerDate: Date;
emailVerified: boolean;
unlocked: boolean;
}
This works fine for all fields. Except the Dates. The Dates are not parsed correctly, so I want to add a custom parser where I can tell him how he gets the date value from the json date string.
Of course now I can add a custom Parser for the whole UserRegistrationState class, but I have Dates on multiple classes and I do not want to create a Parser for every class, because the other properties(except the Dates) are parsed fine.
So my question: Is there a way to add a custom parser for all Date fields?
P.S. This is an example JSON date string, automatically parsed from a Java LocalDateTime:
"registerDate":{"dayOfMonth":17,"dayOfWeek":"WEDNESDAY","dayOfYear":290,"month":"OCTOBER","monthValue":10,"year":2018,"hour":21,"minute":21,"nano":370000000,"second":16,"chronology":{"id":"ISO","calendarType":"iso8601"}}
This unfriendly date format is probably caused by backend. I never had any problems with dates from any API in Angular.
You can create getters/setters to encapsulate custom parsing logic for some field in your class.
In my opinion would be better to fix backend side and move from classes to interfaces in Angular side. Interfaces are better for plain DTO models in TypeScript.

Backward compatibility with JSON

I have a java application which saves the fields of an object to a file and load them later again.
I used so far a modified version of java.util.properties.Properties file (the properties were written in the order they were put in the properties object). I defined for each field a property-key and use the following function to set the property in the properties object
properties.setProperty(PROPERTY_KEY_FIELD_X, field_x);
and used the following function if I want to read back from the properties file
field_x = properties.getProperty(PROPERTY_KEY_FIELD_X, ""));
where I can add a default value which will be choosed, when there is no property with the specified key. As I maybe add some fields in the future in the class from which the fields were saved in the file, I used this option with the default value to set a field to some defined value for the case I load a properties file which was written with an older version of this class.
So far it works but with some obvious disadvantage, for example I can't use the same property key more than once, I have to add some kind of indices like 1_PROPERTY_KEY, 2_PROPERTY_KEY. Thats why I came across JSON.
I'm not very familiar with JSON and all the things we can achieve with it. But I know I could use it to save the fields of an object to the JSON notation and read it back for example with Gson
//Object to JSON
Gson gson = new Gson();
String json = gson.toJson(object);
//JSON to Object
object = gson.fromJson(json);
But I assume this will fail, if the class is a newer version (i.e. some new fields) when try to read back from the JSON. How can I treat this? Do I have to take care of every seperate field similar to the the current implementation with properties? For example like this (with com.google.gson), assuming jsonString is the string read from the file:
//try/catch block omitted
JSONParser parser = new JSONParser();
JSONElement json = parser.parse(jsonString);
field_x = json.getAsString("field_x");
and take care if something failed to set default values to the fields?
Or is there a completely different approach without JSON or is JSON in general suitable for my use-case?
I'm not an expert of JSON too. But I used it sometimes with Jackson and I think Gson is very similar. If you want to use versioned objects just assure in your object constructor that fields are given with a default value (outside correct values range). Jackson will match only those json fields where it finds a correspondent filed in th Java object. Otherwise the default value is kept.
With GSON, you get to define your model as an object. As an example:
public class Configuration
{
private String x;
private String y;
}
Later, you may adjust Configuration to have an additional field, z:
public class Configuration
{
private String x;
private String y;
private String z;
}
When you use GSON to deserialise your JSON file that does not contain a value for z, it will be left null (or whatever the Java default value for that type is).
Since you have an object, you can define getters that substitute a default value if one is not specified:
public String getZ()
{
if (this.z == null)
{
return "the default z value";
}
return this.z;
}
If you're not goind to send data across network, I think you don't need Json but use native Java classes (ie properties). It's reduntant object creation

JAXB Ignore 'extra' elements from Response XML

I am getting a XML response and it keeps on changing very frequently (nodes keep on increasing or reducing). After each updation in response xml my code breaks as my mapped Java class does not have all fileds.
Is there any way to avoid my code breaking if any changes occurs in response XML.
Any help will be appreciated.
Thanks.
Use JAXB.unmarshal() to simply create Java objects from XML.
By default it is very liberal.
Quoting from the javadoc:
In addition, the unmarshal methods have the following characteristic:
Schema validation is not performed on the input XML. The processing will try to continue even if there are errors in the XML, as much as possible. Only as the last resort, this method fails with DataBindingException.
So what JAXB.unmarshal() does is it tries to "transfer" as much data from XML to Java as possible, and it doesn't care if there is no Java field for an XML element or attribute, and it also doesn't care if there is a Java field for which there is no XML element or attribute.
Example
Let's try to unmarshal the following XML to an instance of java.awt.Point:
<p hi="Yo">
<y>123</y>
<peach>weor</peach>
</p>
The Java code:
String s = "<p hi=\"Yo\"><y>123</y><peach>weor</peach></p>";
Point p = JAXB.unmarshal(new StringReader(s), Point.class);
System.out.println(p); // Prints "java.awt.Point[x=0,y=123]"
We told JAXB.unmarshal() to parse a java.awt.Point instance. The input XML contains an element <y> which can be matched with Point.y so an int was parsed and set to Point.y. No XML data was found for Point.x so it was not touched. There were no match for the attribute hi and the XML element <peach>, so they were simply not used for anything.
We got absolutely no Exception here, and the most that was possible was parsed and transferred from XML to Java.
To cope with unknown fields, you can add a List<Object> property annotated #XmlAnyElement(lax=true)
#XmlAnyElement(lax = true)
private List<Object> anything;
Any elements in the input that do not correspond to explicit properties of the class will be swept up into this list. If the element is known to the JAXBContext you'll get the unmarshalled form (the #XmlRootElement annotated class or a JAXBElement<Foo>), if the element is not known to the context you'll get an org.w3c.dom.Element.
Full details in Blaise's blog.
For nodes that get removed you should be fine as long as you use types that can be null (Integer rather than int, Boolean rather than boolean, etc).

How to marshall POJO to JSON using JETTISON?

I have done the marshalling of a JaxB java object to Json using JETTISON. But I can not marshall a simple java object (which has no annotations in it) to Json using JETTISON. I know it is possible to do it by using GSON or MOXy or some other providers.
But I like to get clear "Can we do it using JETTISON?"., If we can, How to do it?
Thanks in Advance.
Don't waste your time, this is simply not what Jettison was designed to do. Conceivably, it would have been possible to instantiate a JSONObject with your POJO and serialize it that way, but there are some issues with its code that make this next to impossible:
It requires passing in the names of the fields that will be included in the JSON.
It can only process public properties of the supplied object.
Not to mention it cannot handle nesting of any kind. Take a look at this lovely code:
Class c = object.getClass();
for (int i = 0; i < names.length; i += 1) {
try {
String name = names[i];
Field field = c.getField(name);
Object value = field.get(object);
this.put(name, value);
} catch (Exception e) {
/* forget about it */
}
}
Yep, thats the code in the constructor JSONObject(Object, String[]). I'm sure you will see the problems with it (raw access to generic objects, can only access public fields, sloppy exception handling). All in all - very bad 'serialization' code.
I know its probably not what you want to hear, but if you want to convert regular Java objects to JSON then you might want to stick with one of the more general-purpose libraries.
JAXB (JSR-222) is configuration by exception and only requires annotations where you need to override the default XML representation (Jettison converts XML StAX events to/from JSON). Instead of #XmlRootElement you can wrap your object in an instance of JAXBElement.
http://blog.bdoughan.com/2012/07/jaxb-no-annotations-required.html

JAXB marshal Set<Object>

I have an object similar to this:
public class Obj {
#XmlElement(name="value")
public Set<Object> values;
}
When marshaling, this is generating an xml like:
<Obj>
<value xsi:type="xs:dateTime" xmlns:xs="http://www.w3.org/2001/XMLSchema">2009-02-14T00:31:30.001+01:00</value>
<value xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">test</value>
</Obj>
However, I want to change some of that values (like the date format used for serializing Date and Timestamp objects), and also get rid of the annoying xsi attributes (but this is not really a requirement, I can live with that)
I've tried adding a #XmlJavaTypeAdapter to values, but in the adapter I get the full Set<Object> to adapt, instead of single elements. I've also tried with a package adapter, but, as my Set is for Object, I cannot put the #XmlJavaTypeAdapter(type) attribute.
Also, I've tried with #XmlJavaTypeAdapter(value=MyAdapter.class, type=Timestamp.class) to get only an adapter for the values inside that Object that I want.
So the question is, does someone know a way to get an adapter to work for this? Or maybe, change the date format every time a Date or Timestamp object is serialized?
Thanks in advance!
#XmlJavaTypeAdapter with the type property has to be specified on the package level. When used in this way it indicates that all usages of that type within the specified package are converted using the XmlAdapter. E.g. if you have a package-info.java like
#XmlJavaTypeAdapters({
#XmlJavaTypeAdaptor(type=Timestamp.class, value=MyAdapter.class)
})
package org.example;
Then a class in that package with a Timestamp field.
package org.example;
public class Obj {
public Timestamp aTimestamp;
}
The specified adapter will be used to convert the timestamp. I suspect that this will work for your Set<Object> case but I haven't tried it myself.
The reason for the xsi:type attribute is that JAX-B likes to produce XML it can deserialize, so it needs to indicate what type it is or it could only parse everything back as strings. You can get rid of this attribute by using the #XmlElementRef annotation to create a schema substitution group, but in this case the XML will be produced with different element names. E.g.
public class Obj {
#XmlElementRefs({
#XmlElementRef(type=String.class, name="string"),
#XmlElementRef(type=Timestamp.class, name="timestamp")
})
public Set<Object> value;
}
Would produce the following XML structure if you had a timestamp and a string in the set. In this scenario the xsi:type attribute is unnecessary since JAX-B can tell what type to create from the element name.
<Obj>
<timestamp>2009-02-14T00:31:30.001+01:00</timestamp>
<string>test</string>
</Obj>
I would strongly recommend using the #XmlElementWrapper annotation to wrap up all the set items if you're going to take this approach.
If all you're after is a simple set of strings that you don't care about deserializing back to Java (or any other) objects with the correct types, then the simplest solution is to have an XmlAdapter that does just adapt the full Set<Object> into a Set<String> and handle the conversion yourself.

Categories