Jackson Json with nested parametric classes - java

Listing:
import java.util.List;
public class Listing<T> {
List<Thing<T>> children;
public List<Thing<T>> getChildren() {
return children;
}
public void setChildren(List<Thing<T>> children) {
this.children = children;
}
}
Thing:
public class Thing<T> {
private String type;
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Link:
public class Link {
private String author;
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
and here's an example of serialization and deserialization...
public static void main(String[] args) throws IOException {
Link link1 = new Link();
link1.setAuthor("JohnDoe");
Link link2 = new Link();
link2.setAuthor("MaryJane");
List<Thing<Link>> things = new ArrayList<Thing<Link>>();
Thing<Link> thing1 = new Thing();
thing1.setData(link1);
thing1.setType("t3");
Thing<Link> thing2 = new Thing();
thing2.setData(link2);
thing2.setType("t3");
things.add(thing1);
things.add(thing2);
Listing<Link> listing = new Listing<Link>();
listing.setChildren(things);
Thing<Listing> thing = new Thing<Listing>();
thing.setType("listing");
thing.setData(listing);
File jsonFile = new File("src/testMap.txt");
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(jsonFile, thing);
//String jsonString = "{\"type\":\"listing\",\"data\":{\"children\":[{\"type\":\"t3\",\"data\":{\"author\":\"JohnDoe\"}},{\"type\":\"t3\",\"data\":{\"author\":\"MaryJane\"}}]}}";
JavaType jsonType = mapper.getTypeFactory().constructParametricType(Thing.class, Listing.class);
Thing<Listing> readThing = mapper.readValue(jsonFile, jsonType);
}
The problem that I'm having is that the Things contained in the Listing in the sample code above are not parametrized with Link, so their data field is returned as an Object (which is actually LinkedHashMap).
I want to be able to do something like this:
List<Thing<Link>> readListingChildren = readThing.getData().getChildren();
String author = readListingChildren.get(0).getData().getAuthor();
My question is, how would I get this to work using Jackson json?
Note: there will be multiple different types of objects contained by Things, and a Thing's data member's type is defined (or should be defined) by the data object's "type" field, using strings such as t1, t2, t3, etc. which map to different classes.

To achieve a serialized String like
{
"data":{
"type":"listing",
"children":[
{
"data":{
"type":"t3",
"author":"JohnDoe"
}
},
{
"data":{
"type":"t3",
"author":"MaryJane"
}
}
]
}
}
and to use the type information to correctly deserialize the concrete class you may use
#JsonTypeName("listing")
public class Listing<T> {
List<Thing<T>> children;
public List<Thing<T>> getChildren() {
return children;
}
public void setChildren(final List<Thing<T>> children) {
this.children = children;
}
}
public class Thing<T> {
private T data;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(Link.class),
#JsonSubTypes.Type(Listing.class)
})
public T getData() {
return data;
}
public void setData(final T data) {
this.data = data;
}
}
#JsonTypeName("t3")
public class Link {
private String author;
public String getAuthor() {
return author;
}
public void setAuthor(final String author) {
this.author = author;
}
}

Related

JSON to Java Object deserializer POJO Eror: Could not resolve subtype

I have a problem related to:
De-serializing JSON to polymorphic object model using Spring and JsonTypeInfo annotation
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id '[' as a subtype
The solutions provided there didn't work for me.
I have the following DTO:
public class QuestionaireAnswersDTO {
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = As.EXISTING_PROPERTY)
#JsonSubTypes({
#JsonSubTypes.Type(name = "single", value = SingleChoiceAnswerDTO.class),
#JsonSubTypes.Type(name = "multi", value = MultipleChoiceAnswerDTO.class)
})
public static abstract class QuestionaireAnswerDTO {
String answerId;
String name;
public String getAnswerId() {
return answerId;
}
public void setAnswerId(String answerId) {
this.answerId = answerId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
String questionnaireId;
List<QuestionaireAnswerDTO> answers;
public String getQuestionnaireId() {
return questionnaireId;
}
public void setQuestionnaireId(String questionnaireId) {
this.questionnaireId = questionnaireId;
}
public List<QuestionaireAnswerDTO> getAnswers() {
return answers;
}
public void setAnswers(List<QuestionaireAnswerDTO> answers) {
this.answers = answers;
}
with those subclasses:
public static class SingleChoiceAnswerDTO extends QuestionaireAnswerDTO {
#Nullable
String selectedOption;
public String getSelectedOption() {
return selectedOption;
}
public void setSelectedOption(String selectedOption) {
this.selectedOption = selectedOption;
}
}
public static class MultipleChoiceAnswerDTO extends QuestionaireAnswerDTO {
List<String> selectedOptions;
public List<String> getSelectedOptions() {
return selectedOptions;
}
public void setSelectedOptions(List<String> selectedOptions) {
this.selectedOptions = selectedOptions;
}
}
Now I wanted to write a test using this json object:
{
"questionnaireId":"questionnaire1",
"answers":[
{
"name":"single",
"answerId":"Question1",
"selectedOption":"Yes"
},
{
"name":"multi",
"answerId":"Question3",
"selectedOptions":[
"yes",
"no"
]
}
]
}
Using this test:
JsonFactory factory = new JsonFactory();
factory.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
ObjectMapper mapper = new ObjectMapper(factory);
mapper.registerSubtypes(QuestionaireAnswersDTO.SingleChoiceAnswerDTO.class, QuestionaireAnswersDTO.MultipleChoiceAnswerDTO.class);
QuestionaireAnswersDTO result = mapper.readValue(testData, QuestionaireAnswersDTO.class);
String resultAsString = mapper.writeValueAsString(result);
System.out.println(resultAsString);
Which results in the following Error:
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, (...)
missing type id property '#class' (for POJO property 'answers')
Using the .registerSubtypes() method instead of JsonSubtypes didn't work here instead of JsonSubtypes. Same error occurs.

Mapping dynamic json object from parameter to POJO

I am using resttemplate to get a json response body, which looks like this :
{
"entities": {
"Q11649": {
"type": "item",
"id": "Q11649",
"sitelinks": {
"enwiki": {
"site": "enwiki",
"title": "Nirvana (Band)",
"badges": []
},
..
The object "Q11649" is based on a url parameter called ids, for example :
https://www.wikidata.org/w/api.php?action=wbgetentities&format=json&props=sitelinks&ids=Q11649
this part at the end ids=Q11649
I only want the title value from under enwiki key, however the ids is dynamic so I can't create a POJO to map it to that id.
How can create a dynamic resttemplate method to extract only the title from this json structure?
I have a Wikidata class that looks like this :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Wikidata {
#JsonProperty("entities")
private Entity entity;
public Wikidata () {
}
public Wikidata(Entity entity) {
this.entity = entity;
}
public Entity getEntities() {
return entity;
}
public void setEntities(Entity entity) {
this.entity = entity;
}
}
After reading a few articles, I realise that a map is probably the best way to go, this is how my entity class looks like :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Entity {
#JsonProperty()
private Map<Object, Object> data;
public Entity () {
data = new HashMap<>();
}
public Map<Object, Object> getData(){
return data;
}
#JsonAnySetter
public void add(String property, String value){
data.put(property, value);
}
}
My Sitelinks class :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Sitelinks {
private Enwiki enwiki;
public Sitelinks () {
}
public Sitelinks(Enwiki enwiki) {
this.enwiki = enwiki;
}
public Enwiki getEnwiki() {
return enwiki;
}
public void setEnwiki(Enwiki enwiki) {
this.enwiki = enwiki;
}
}
And my Enwiki class :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Enwiki {
private String title;
public Enwiki () {
}
public Enwiki(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
How can I use this map to link between the dynamic id and my sitelinks class so that I can get the title value?

How to access in Array List model class

This is array list model class. Is it right or wrong? I can not access this class from another class
public class CartOrder {
List<CartOrder.Data> data;
public List<Data> getData() {
return data;
}
public void setData(List<Data> data) {
this.data = data;
}
class Data {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
If you would have read the exception properly, then you would have also read the solution to your problem. You need to instantiate CartOrder object first on which you then can call s.new Data() as seen below
public static void main(String[] args) {
CartOrder s = new CartOrder();
Data d = s.new Data();
d.setName("Test");
s.addData(d);
System.out.println(s);
}
Modified your existing class:
public class CartOrder {
List<Data> data;
public CartOrder () {
data = new ArrayList<Data>();
}
public List<Data> getData() {
return data;
}
public void setData(List<Data> data) {
this.data = data;
}
public void addData(Data data) {
this.data.add(data);
}
class Data {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
similar question here
But yes your code is accurate. If you make your member property private Data::name then it cannot be accessed by anything but the factory class (class Data). Same would be true for your other function and its member.
I'm assuming you want to use it like:
CartOrder.Data data = new CartOrder.Data();
In order for that to work you should make you inner class static, so you code would become
public class CartOrder {
List<CartOrder.Data> data;
public List<Data> getData() {
return data;
}
public void setData(List<Data> data) {
this.data = data;
}
static class Data {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
After that you can use this like:
public void someMethodInSomeOtherClass() {
// ...
CartOrder cartOrder = new CartOrder();
CartOrder.Data someData = new CartOrder.Data();
someData.setName("Luke");
CartOrder.Data moreData = new CartOrder.Data();
moreData.setName("Han");
cartOrder.setData(Arrays.asList(someData, moreData));
// ...
}

Marshall a List to XML works - but how to unmarshall?

I can marshall a ObservableList using a "Wrapper"-class like below. But I cannot unmarshall it back to the wrapperclass it was before.
The idea is:
I have an ObservableList of "Expenses". I put this List into a wrapper-class and save this class to XML. The result looks like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<List>
<root>
<category>[none]</category>
<period>Year</period>
<title>asd</title>
<value>354</value>
</root>
</List>
I cannot bring it back to the wrapper-object.
I really appreciate any kind of help.
Main-class JAXBContext (visible for all):
JAXBContext jc = JAXBContext.newInstance(MyWrapperForList.class, Expense.class);
Main-class SAVEBUTTON:
public class SaveButtonListener implements EventHandler<ActionEvent> {
#Override
public void handle(ActionEvent arg0) {
File serializedFile = new File(PATH);
try {
if (serializedFile.exists() == false)
serializedFile.createNewFile();
PrintWriter xmlOut = new PrintWriter(serializedFile);
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
List<Expense> saveList = new ArrayList<>();
saveList.addAll(data);
MyWrapperForList<Expense> wrapper = new MyWrapperForList<>(saveList);
JAXBElement<MyWrapperForList> jaxbElement = new JAXBElement<>(
new QName("List"), MyWrapperForList.class, wrapper);
m.marshal(jaxbElement, xmlOut);
xmlOut.flush();
xmlOut.close();
Main-class-LOADBUTTON:
public class LoadButtonListener implements EventHandler<ActionEvent> {
#Override
public void handle(ActionEvent arg0) {
try {
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource xml = new StreamSource(PATH);
MyWrapperForList<Expense> unwrapper = unmarshaller.unmarshal(xml,
MyWrapperForList.class).getValue();
List<Expense> tempList = new ArrayList<>();
tempList.addAll(unwrapper.getItems());
System.out.println(tempList.get(0).getTitle());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Wrapper-class:
public class MyWrapperForList {
private List<Expense> list;
public MyWrapperForList() {
list = new ArrayList<>();
}
public MyWrapperForList(List<Expense> expenses) {
this.list = expenses;
}
#XmlAnyElement(lax=true)
public List<Expense> getItems() {
return list;
}
}
Expense-class:
#XmlRootElement(name = "root")
public class Expense {
private String title;
private String category;
private String period;
private String value;
public Expense() {} //Default constructor is needed for XML-handling
public Expense(String title, String value, String period, String category) {
this.title = title;
this.value = value;
this.period = period;
this.category = category;
}
#XmlElement(name = "title")
public String getTitle() {
return this.title;
}
#XmlElement(name = "category")
public String getCategory() {
return this.category;
}
#XmlElement(name = "period")
public String getPeriod() {
return this.period;
}
#XmlElement(name = "value")
public String getValue() {
return this.value;
}
}
I used this tutorial from Blaise Doughan: http://blog.bdoughan.com/2012/11/creating-generic-list-wrapper-in-jaxb.html
MyListWrapper
If you want MyWrapperForList to unmarshal holding an instance of ObservableList then you will need to setup your class in one of the following ways.
Property of Type ObservableList
import javax.xml.bind.annotation.XmlAnyElement;
import javafx.collections.*;
public class MyWrapperForList<T> {
private ObservableList<T> list;
public MyWrapperForList() {
list = FXCollections.<T>observableArrayList();
}
public MyWrapperForList(ObservableList<T> list) {
this.list = list;
}
#XmlAnyElement(lax = true)
public ObservableList<T> getItems() {
return list;
}
}
List Property Initialized to Instance of ObservableList
import java.util.List;
import javax.xml.bind.annotation.XmlAnyElement;
import javafx.collections.*;
public class MyWrapperForList<T> {
private List<T> list = FXCollections.<T>observableArrayList();
public MyWrapperForList() {
list = FXCollections.<T>observableArrayList();
}
public MyWrapperForList(List<T> list) {
this.list = list;
}
#XmlAnyElement(lax = true)
public List<T> getItems() {
return list;
}
}
Demo Code
Input (nub.xml)
<List>
<root>
<category>[none]</category>
<period>Year</period>
<title>dfg</title>
<value>4</value>
</root>
<root>
<category>[none]</category>
<period>Year</period>
<title>ROBO</title>
<value>1234</value>
</root>
</List>
Demo
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(MyWrapperForList.class, Expense.class);
//UNMARSHALLING
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource xml = new StreamSource("src/forum18594548/nub.xml");
MyWrapperForList<Expense> wrapper = (MyWrapperForList<Expense>) unmarshaller.unmarshal(xml, MyWrapperForList.class).getValue();
List<Expense> data = wrapper.getItems();
System.out.println(data.getClass());
for(Expense expense : data) {
System.out.println(expense);
}
}
}
Output
class com.sun.javafx.collections.ObservableListWrapper
forum18594548.Expense#789df61d
forum18594548.Expense#4a8927c8
UPDATE
First: Thanks for you work Blaise!! I'm really glad for what you do to
me! I tried what you wrote here (it was nearly the same as I had) and
I got a similar (same type of) output as you got. BUT the objects in
the lists are all referenced with null. If I write
System.out.println(data.get(0).getTitle()); it says null. There is the
exact amount of objects in the list, but all attributes are referenced
with null. :(
I think I got tunnel vision on the ObservableList aspect only to miss your real problem was with how you mapped the Expense class. Since you only have get methods you should map to the fields using #XmlAccessorType(XmlAccessType.FIELD) as follows.
import javax.xml.bind.annotation.*;
#XmlRootElement(name="root")
#XmlAccessorType(XmlAccessType.FIELD)
public class Expense {
private String title;
private String category;
private String period;
private String value;
public Expense() {
}
public Expense(String title, String value, String period, String category) {
this.title = title;
this.value = value;
this.period = period;
this.category = category;
}
public String getTitle() {
return this.title;
}
public String getCategory() {
return this.category;
}
public String getPeriod() {
return this.period;
}
public String getValue() {
return this.value;
}
}

Using Jackson to parse Json map value into String or CustomClass

I'm being given a Json file with the form:
{
"descriptions": {
"desc1": "someString",
"desc2": {"name":"someName", "val": 7.0}
}
}
I have the POJO:
public class CustomClass {
Map<String, Object> descriptions;
public static class NameVal{
String name;
double val;
public NameVal(String name, double val){...}
}
}
I can recreate the json file with the code:
CustomClass a = new CustomClass();
a.descriptions = new HashMap<String, Object>();
a.descriptions.put("desc1", "someString");
a.descriptions.put("desc2", new CustomClass.NameVal("someName", 7.0));
new ObjectMapper().writeValue(new File("testfile"), a);
But, when I read the object back in using:
CustomClass fromFile = new ObjectMapper().readValue(new File("testfile"), CustomClass.class);
then fromFile.descriptions.get("desc2") is of type LinkedHashMap instead of type CustomClass.NameVal.
How can I get Jackson to properly parse the type of the CustomClass.NameVal descriptors (other than making some class that wraps the parsing and explicitly converts the LinkedHashMap after Jackson reads the file)?
Try this. Create a class Description with name and value attributes:
public class Description {
private String name;
private double val;
}
Now in your CustomClass do this:
public class CustomClass {
List<Description> descriptions;
}
And that's it. Remember to create getters and setters because Jackson needs it.
You could try something like this:
public class DescriptionWrapper {
private Description descriptions;
public Description getDescriptions() {
return descriptions;
}
public void setDescriptions(Description descriptions) {
this.descriptions = descriptions;
}
}
public class Description {
private String desc1;
private NameValue desc2;
public String getDesc1() {
return desc1;
}
public void setDesc1(String desc1) {
this.desc1 = desc1;
}
public NameValue getDesc2() {
return desc2;
}
public void setDesc2(NameValue desc2) {
this.desc2 = desc2;
}
}
public class NameValue {
private String name;
private double val;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getVal() {
return val;
}
public void setVal(double val) {
this.val = val;
}
}

Categories