Using JAXB to unmarshall elements with varying/dynamic names - java

I'm parsing an XML document that has nodes like the following:
<objects>
<dog>
<data1>...</data1>
<data2>...</data2>
<data3>...</data3>
</dog>
<cat>
<data1>...</data1>
<data2>...</data2>
<data3>...</data3>
</cat>
</objects>
The elements data1, data2, data3 are always consistent. Only the parent tag varies. In my object model I have a single Object which represents all of these cases. How can I get JAXB to handle this case without knowing in advance the name of the element?
#XMLAnyElement matches all the objects but doesn't create an object of the appropriate type; I get a list of Node objects instead of my object. My object currently looks something like:
public class MyObject {
protected String otherData;
#XmlAnyElement
#XmlElementWrapper(name = "objects")
protected List<MyChildObject> childObjects;
}
public class MyChildObject {
protected String data1;
protected String data2;
protected String data3;
}
Any ideas how to handle this case short of changing the incoming XML format to use <object type="dog"> elements?

If the name is truely dynamic, then I don't think JAXB will help you these. If you have a defined number of various element names then you could use inheritance like the other post suggested. If the number of elements and names is unknown I would recommend using something like this:
#XmlMixed
#XmlAnyElement
public List<Object> getObjects() {
return objects;
}
This would bring the element is a just a DOM element. You could then use JAXB a second time to go from each of the elements into your custom type.
That would be if you had to use JAXB. You might find it easier to just use the SAX or DOM APIs directly for data like this. JAXB is really intended for well defined data that can be represented as a schema.

You can use inheritance:
#XmlRootElement(name = "dog")
public class MyDogObject extends MyChildObject {
//nothing here
}
#XmlRootElement(name = "cat")
public class MyCatObject extends MyChildObject {
//nothing here
}
This way it lets you deal with the same object type, MyChildObject, yet flexibly control the XML structure. Another benefit is that should you define specific dog/cat XML nodes in the future - they can be mapped on that corresponding subclass but not the other, as expected.

Related

Mapping POJOs to case classes with immutable lists during deserialisation

I am coming from Java background and trying to understand how to model Domain classes/POJOs in Scala.
I am trying to Deserialize JSON response from a RestAPI and my Java POJOs are as follows:
#Data
public class ColumnResponse {
private String id;
private String name;
private String type;
...
}
k
#Data
public class DataSetGetResponse {
private String id;
private List<ColumnResponse> columns;
...
}
Now I have created following Case Classes
case class DataSetGetResponse (id: String,
columns: List[ColumnResponse]
.... )
case class ColumnResponse (id: String,name: String ...)
I am trying to use https://sttp.readthedocs.io/en/latest/json.html#json4s library for HTTP communication and json4s for deserialization.
Questions:
1) In the DataSetGetResponse case class, field "columns" is a List.By default this is an immutable list. How the Deserialization library add new DataColumnGetResponse objects to this immutable list? Do I have to declare this as mutable ?
2) There is a field called 'type' field in the ColumnResponse POJO. In Scala 'type' is a reserved keyword.How to handle this case?
Answer the first one:
An immutable object can be mutated with the copy function:
dataSet.copy(columns = newResp :: dataSet.columns)
For more complex tasks you can use Lenses see for example here: enter link description here
Answer the second one:
If it is a reserved word you can do it like
case class ColumnResponse (id: String, name: String, `type`: String)
This answer addresses the following aspect of the question:
How the Deserialization library add new DataColumnGetResponse objects
to this immutable list?
Let us consider a simplified version of the problem:
JsonMethods.parse("""[1,2,3]""").extract[List[Int]]
How does json4s deserialise [1,2,3] into immutable List[Int]? First it parses the raw string into an intermediary AST (abstract syntax tree) data structure where it represents the list like so
case class JArray(arr: List[JValue]) extends JValue
We see here that arr is an immutable list. The key line that builds it up after parse executes is in JsonParser
def newValue(v: JValue): Unit = {
...
case a: JArray => vals.replace(JArray(v :: a.arr))
...
}
Note how the operator :: in v :: a.arr adds an element at the beginning of this list and returns a new list with v added in. This means since there are three elements in [1,2,3] the following three lists are created by json4s in the process of deserialisation
JArray(List(JInt(1))
JArray(List(JInt(2), JInt(1)))
JArray(List(JInt(3), JInt(2), JInt(1)))
Again note these are three separate lists.
Next, after internal AST is created, actual deserialisation to List[Int] takes place by calling extract[List[Int]]. The key component that does this for lists is CollectionBuilder
private class CollectionBuilder(json: JValue, tpe: ScalaType)(implicit formats: Formats) {
...
val array: Array[_] = json match {
case JArray(arr) => arr.map(extractDetectingNonTerminal(_, typeArg)).toArray
...
}
Note how we simply map over AST arr built up during parsing step and convert each element to the model of type typeArg, which in our simple case is Int but in your case would be DataColumnGetResponse.

Deserializing multiple fields in Jackson

Suppose I have JSON that looks something like this:
{ "key1":1, "key2":2, "key3":3 }
Where the number of "key-n" fields is unknown, but are consecutively numbered starting at 1. I wish to deserialize it into an object as follows:
public class MyPojo {
private List<Integer> keys;
}
That is, keys.get(0) corresponds to the key1 field, and so on. The JSON may have other non-"key-n" fields as well.
I had been under the impression that something like
public class MyPojo {
#JsonUnwrapped #JsonDeserialize(using = KeyDeserializer.class) private List<Integer> keys;
}
where KeyDeserializer is a JsonDeserializer would just extract all of the "key-n" fields, would work; however, I had discovered that the deserializer isn't being invoked because the JSON lacks a field named key.
Since the JSON is third-party, I can't really try to modify the JSON, so I am wondering if there are any alternate approaches to this problem.

Jaxb/MOXY ClassExtractor on XmlElement

I'm facing an issue using jaxb, when unmarshalling my xml, I can't manage to make the difference between one of my classes and an inherited one.
I got a 1 st class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlClassExtractor(PersonExtractor.class)
#XmlSeeAlso(Toto.class)
public class Person {
}
An inherited one:
public class Toto
extends Person
{
#XmlElement(name = "additionalInformation")
private String additionalInformation;
}
My extractor:
public class PersonExtractor
extends ClassExtractor
{
#Override
public Class extractClassFromRow(Record pArg0, Session pArg1)
{
if (pArg0.get("additionalInformation") != null || pArg0.get("#additionalInformation") != null)
{
return Toto.class;
}
else
{
return Person.class;
}
}
}
Unfortunately this doesn't works.
When I'm trying to unmarshall Person.class is always returned.
If I change:
#XmlElement(name = "additionalInformation")
into
#XmlAttribute(name = "additionalInformation")
Everything works fine (unfortunately I do want an element).
None of those classes is my root element (everything is "deep" into my model)
Does someone know what I am missing ? probably somthing really dumb, but can't manage to put my hand on it.
Thanks
Probably you have a namespace define for your root element (or default namespace), so the element name is not additionalInformation but namespace:additionalInformation (or however it is represented in the Record element). Have you also checked that content of Toto class is saved correctly witht he additionalInformation element (I guess it was since it works with attribute, but just in case).
You can printout the content of your Record element to see what and how is stored there.
Thanks for the answer, I found a solution:
Apparently currently MOXy requires that the inheritance indicator is in an XML attribute for the #XmlClassExtractor to work.
I ended up finding and using an other way to handle inheritance using #XmlAdapter

Unmarshalling empty tag into new instances of collections

I am trying to unmarshal something like below into their JAXB equivalents, and the fields are as expected being populated with nulls
XML
<University>
<StudentFirstNames/>
</University>
JAXB POJO
public class University {
List<StudentFirstNames> studentFirstNames = null;
public void getStudentFirstNames() {
return studentFirstNames
}
public void setStudentFirstNames() {
this.studentFirsNames = studentFirstNames;
}
}
After unmarshalling I am returned back null when I lookup the member in the JAXB object
university.getStudentFirstNames() --> null
In this specific situation I am trying to resolve, I need to replace empty tags with new instance of that particular type. For example in the above, I am expecting back a new ArrayList () instead of null. I know this sounds counter intuitive but that is what I have to do for satisying code downstream.
Is there a global fix to resolve such instances. Thanks in advance.
You could initialize your field as follows to get the desired behaviour:
List<StudentFirstNames> studentFirstNames = new ArrayList<StudentFirstNames>;
To be able to differentiate between a null and empty collection you will need to use an #XmlElementWrapper annotation.
http://blog.bdoughan.com/2012/12/jaxb-representing-null-and-empty.html

Deserialization of generic collections with Gson

I have some difficulties with json deserialization using GSon and I hope somebody can help me.
I want to deserialize the following json snippet:
{
"fieldA": "valueA",
"myCollection": {
"AnotherClass": [
{
"objectAfieldA": "valueB",
"objectAfieldB": "valueC"
},
{
"objectAfieldA": "valueD",
"objectAfieldB": "valueE"
}
]
}
}
the corresponding overall class has following fields:
...
String fieldA;
List<AnotherClass> = new ArrayList<AnotherClass>();
....
Now, my problem is that when I deserialize, using fromJson(jsonSample, resultContainer.class), without the List<T> element, everything is good, but I get a NullPointerException when I include the contained list. I've read about how to deal with collections of generic types and the use of TypeToken, but I can't apply this knowledge when my collection is part of another class…
I really would appreciate any help to solve this.
The solution for deserealizing the unnamed JSON array is quite simple:
List<resultContainer> lres = gson.fromJson(new FileReader("input.json"), new TypeToken<List<resultContainer>>(){}.getType());
When deserializing, you only need to use the TypeToken if the outer-most structure to be deserialized into is a generic collection. This is not the case for the example in the original question. So, use of a TypeToken is unnecessary.
The issue appears to be that the JSON structure does not match the Java structure attempting to be bound to.
The JSON structure defines
an object with two elements
element 1 is a string named "fieldA",
element 2 is an object named "myCollection", which has one element
the one element is an array named "AnotherClass", composed of objects with two elements
element 1 is a string named "objectAfieldA",
element 2 is a string named "objectAfieldB"
So, define a Java data structure to match that, and deserialization will work very simply, without any custom processing necessary. If such a matching Java structure is not provided, then custom deserialization is necessary.
Here is such a working example using the names and types from the original question.
import java.io.FileReader;
import com.google.gson.Gson;
public class Foo
{
public static void main(String[] args) throws Exception
{
Gson gson = new Gson();
resultContainer result = gson.fromJson(new FileReader("input.json"), resultContainer.class);
System.out.println(gson.toJson(result));
}
}
class resultContainer
{
String fieldA;
MyCollectionContainer myCollection;
}
class MyCollectionContainer
{
SomeOtherClass[] AnotherClass;
}
class SomeOtherClass
{
String objectAfieldA;
String objectAfieldB;
}

Categories

Resources