Realm can't create Nested Objects from Json - java

I've been trying to create a nested RealmObject using a json but it only creates the first Object and not the nested ones. I would appreciate a help on this.
my Realm classes:
Content.java
public class Content extends RealmObject {
private String uuid;
RealmList<ContentDetailModel> ContentDetail;
public Content() {
super();
this.uuid = UUID.randomUUID().toString();
}
public String getUuid() {
return uuid;
}
public RealmList<ContentDetailModel> getContentDetails() {
return ContentDetail;
}
public void setContentDetails(RealmList<ContentDetailModel> contentDetails) {
this.ContentDetail = contentDetails;
}
}
ContentDetailModel.java:
public class ContentDetailModel extends RealmObject {
String FileName;
String ContentTypeID;
RealmList<ContentDetailMetadataModel> ContentDetailMetadata;
RealmResults<Content> content = null;
public String getFileName() {
return FileName;
}
public void setFileName(String fileName) {
FileName = fileName;
}
public String getContentTypeID() {
return ContentTypeID;
}
public void setContentTypeID(String contentTypeID) {
ContentTypeID = contentTypeID;
}
public RealmList<ContentDetailMetadataModel> getContentDetailMetadata() {
return ContentDetailMetadata;
}
public void setContentDetailMetadata(RealmList<ContentDetailMetadataModel> contentDetailMetadataz) {
this.ContentDetailMetadata = contentDetailMetadataz;
}
}
and the rest of nested classes are like these. my Json string is as follows:
"
{
"Content":{
"ContentDetail":[
{
"FileName":"test.mp3",
"ContentTypeID":3,
"ContentDetailMetadata":{
"Metadata":[
{
"ID":2,
"Value":"2017-08-02 09:40:30"
},
{
"ID":1,
"Value":"35.73876557934912,51.50785446166992"
}
]
}
},
{
"FileName":"2.jpg",
"ContentTypeID":2,
"ContentDetailMetadata":[
{
"Metadata":{
"ID":2,
"Value":"2017-08-02 09:40:30"
}
},
{
"Metadata":{
"ID":1,
"Value":"35.73876557934912,51.50785446166992"
}
}
]
}
]
}
}
"
and the code I use to do it is :
realm.createObjectFromJson(json)

{
"Content":{
"ContentDetail":[
{
"FileName":"test.mp3",
"ContentTypeID":3,
"ContentDetailMetadata":[{
"Metadata":[
{
"ID":2,
"Value":"2017-08-02 09:40:30"
},
{
"ID":1,
"Value":"35.73876557934912,51.50785446166992"
}
]
}]
},
Translates to:
public class Root extends RealmObject {
private Content Content;
}
public class Content extends RealmObject {
private RealmList<ContentDetail> ContentDetail;
#LinkingObjects("Content")
private final RealmResults<Root> roots = null;
}
public class ContentDetail extends RealmObject {
private String FileName;
private long ContentTypeID;
//private ContentDetailMetadata ContentDetailMetadata;
private RealmList<ContentDetailMetadata> ContentDetailMetadata;
#LinkingObjects("ContentDetail")
private final RealmResults<Content> contents = null;
}
public class ContentDetailMetadata extends RealmObject {
private RealmList<Metadata> Metadata;
#LinkingObjects("ContentDetailMetadata")
private final RealmResults<ContentDetail> contentDetails = null;
}
public class Metadata extends RealmObject {
private long ID;
private String Value;
#LinkingObjects("Metadata")
private final RealmResults<ContentDetailMetadata> contentDetailMetadatas = null;
}
If your schema doesn't look like that, then createOrUpdateFromJson() won't work.
Personally I would advise against using this schema though, it's pretty bad as a Realm schema. It's advisable to parse the JSON and then map it into a schema that makes more sense!

It looks like your JSON has put all fields for the Content object under the Context JSON object instead of directly under the top-level object where it should be.
If you do this, it should work:
realm.createObjectFromJson(Content.class, json.getJSONObject("Content"));

Related

How to parse complex nested JSON in java?

I have a complex nested Json
It has a body similar to this:
{
staus: "Success",
id: 1,
data: [{'Movie':'kung fu panda','% viewed': 50.5},{'Movie':'kung fu panda 2','% viewed':1.5}],
metadata: {'filters':['Movie', 'Percentage Viewed'], 'params':{'content':'Comedy', 'type': 'Movie'}}
}
The only field I care about is data, and metadata is usually an even more complex/nested field. I was trying to map this to:
#JsonIgnoreProperties(ignoreUnknown = true)
class ResponseData{
public Data[] data;
#JsonIgnoreProperties(ignoreUnknown = true)
class Data{
public String Movie;
public double viewed;
}
}
I was looking at Jackson as an option and writing my own serializer and use JsonIgnore to discard the metadata but can't get around it.
Any suggestion on how this could be done?
You can use jackson-utils
public class Foo {
public static void main(String... args) {
ResponseData responseData1 = new ResponseData(
1,
"Success",
new ResponseData.Data[] {
new ResponseData.Data("kung fu panda", 50.5),
new ResponseData.Data("kung fu panda 2", 1.5) },
new ResponseData.Metadata(
new HashSet<>(Arrays.asList("Movie", "Percentage Viewed")),
new ResponseData.Metadata.Params("Comedy", "Movie"))
);
String json = JacksonUtils.prettyPrint().writeValue(responseData1);
System.out.println(json);
ResponseData responseData2 = JacksonUtils.readValue(json, ResponseData.class);
}
}
class ResponseData {
private int id;
private String status;
private Data[] data;
private Metadata metadata;
public ResponseData() {
}
public ResponseData(int id, String status, Data[] data, Metadata metadata) {
this.id = id;
this.status = status;
this.data = data;
this.metadata = metadata;
}
public static class Data {
#JsonProperty("Movie")
private String movie;
#JsonProperty("% viewed")
private double viewedPercents;
public Data() {
}
public Data(String movie, double viewedPercents) {
this.movie = movie;
this.viewedPercents = viewedPercents;
}
}
public static class Metadata {
private Set<String> filters;
private Params params;
public Metadata() {
}
public Metadata(Set<String> filters, Params params) {
this.filters = filters;
this.params = params;
}
public static class Params {
private String content;
private String type;
public Params() {
}
public Params(String content, String type) {
this.content = content;
this.type = type;
}
}
}
}
Console output:
{
"id" : 1,
"status" : "Success",
"data" : [ {
"Movie" : "kung fu panda",
"% viewed" : 50.5
}, {
"Movie" : "kung fu panda 2",
"% viewed" : 1.5
} ],
"metadata" : {
"filters" : [ "Movie", "Percentage Viewed" ],
"params" : {
"content" : "Comedy",
"type" : "Movie"
}
}
}
P.S. As an alternative, there is another util gson-utils with the same syntax.

Cannot deserialize instance of `org.json.JSONObject`

I have a basic SpringBoot 2.1.5.RELEASE app. Using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file with some RestControllers.
In 1 of the controller this is the body I send:
{
"depositHotel": "xxx",
"destinationHotel": "aaa",
"depositHotelAmount": "0.2",
"destinationHotelAmount": "4",
"destinationAddress": [{
"address": "asdf",
"tag": ""
}],
"refundAddress": [{
"address": "pio",
"tag": ""
}]
}
so I create this class to use it as a RequestBody:
public class HotelswitchHotelOrderRequestBody {
public static class Builder {
private String depositHotel;
private String destinationHotel;
private Float depositHotelAmount;
private Float destinationHotelAmount;
private JSONObject destinationAddress;
private JSONObject refundAddress;
public Builder(String depositHotel, String destinationHotel) {
this.depositHotel = depositHotel;
this.destinationHotel = destinationHotel;
}
public Builder withDepositHotelAmount (Float depositHotelAmount) {
this.depositHotelAmount = depositHotelAmount;
return this;
}
public Builder withDestinationHotelAmount (Float destinationHotelAmount) {
this.destinationHotelAmount = destinationHotelAmount;
return this;
}
public Builder toDestinationAddress (JSONObject destinationAddress) {
this.destinationAddress = destinationAddress;
return this;
}
public Builder toRefundAddress (JSONObject refundAddress) {
this.refundAddress = refundAddress;
return this;
}
public HotelswitchHotelOrderRequestBody build(){
HotelswitchHotelOrderRequestBody order = new HotelswitchHotelOrderRequestBody();
order.depositHotel = this.depositHotel;
order.depositHotelAmount = this.depositHotelAmount;
order.destinationAddress = this.destinationAddress;
order.destinationHotel = this.destinationHotel;
order.destinationHotelAmount = this.destinationHotelAmount;
order.refundAddress = this.refundAddress;
return order;
}
}
private String depositHotel;
private String destinationHotel;
private Float depositHotelAmount;
private Float destinationHotelAmount;
private JSONObject destinationAddress;
private JSONObject refundAddress;
private HotelswitchHotelOrderRequestBody () {
//Constructor is now private.
}
public String getDepositHotel() {
return depositHotel;
}
public void setDepositHotel(String depositHotel) {
this.depositHotel = depositHotel;
}
public String getDestinationHotel() {
return destinationHotel;
}
public void setDestinationHotel(String destinationHotel) {
this.destinationHotel = destinationHotel;
}
public Float getDepositHotelAmount() {
return depositHotelAmount;
}
public void setDepositHotelAmount(Float depositHotelAmount) {
this.depositHotelAmount = depositHotelAmount;
}
public Float getDestinationHotelAmount() {
return destinationHotelAmount;
}
public void setDestinationHotelAmount(Float destinationHotelAmount) {
this.destinationHotelAmount = destinationHotelAmount;
}
public JSONObject getDestinationAddress() {
return destinationAddress;
}
public void setDestinationAddress(JSONObject destinationAddress) {
this.destinationAddress = destinationAddress;
}
public JSONObject getRefundAddress() {
return refundAddress;
}
public void setRefundAddress(JSONObject refundAddress) {
this.refundAddress = refundAddress;
}
}
But I have this error when receiving the object:
JSON parse error: out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `org.json.JSONObject` out of START_ARRAY token
JSONObject's representation in actual JSON is a hash i.e. {...}. In your json data you're providing an array of hashes [{...}] which is not the same. Judging from your domain I don't think it has to be multiple values, so you can just omit [] in your payload and if it does then the fields in your Java class can be defined as JSONArray.
However, I think you should go with defining an Address class and either using
private Address destinationAddress;
private Address refundAddress;
or if it indeed does have to be an object array
private List<Address> destinationAddresses;
private List<Address> refundAddresses;
I had a similar usecase , where I could not define the json to a POJO. Using com.fasterxml.jackson.databind.JsonNode instead of the JSONObject worked.

Json to Java Object with same key but different objects inisde

I have a question how to map JSON Objects with same key to different objects.
If I write 2 classes with the specific fields I get an error because the data element is contained twice.
How can I map the resource A to class A and the resource B to class B?
{
"ETag":"oiojojojojoj==",
"elements":[
{
"resource":"A",
"ETag":null,
"data":[
{
"anotherfield":"text",
"id":1,
"name":"GB"
}
]
},
{
"resource":"B",
"ETag":"dmVuZG9yOzIwMTctMDUtMDlUMTQ6MzE6NDNa",
"data":[
{
"field":"read",
"id":1
}
]
}
]
}
The classes could look like this:
public class Element {
#JsonProperty("resource")
public String resource = null;
#JsonProperty("ETag")
public Object eTag = null;
#JsonProperty("data")
public List<A> dataInA = null;
#JsonProperty("data")
public List<B> dataInB = null;
}
public class Example {
#JsonProperty("ETag")
public String eTag;
#JsonProperty("elements")
public List<Element> elements = null;
}
Class A
public class A {
#JsonProperty("anotherfield")
public String anotherfield;
#JsonProperty("id")
public Integer id;
#JsonProperty("name")
public String name;
}
Class B
public class B {
#JsonProperty("field")
public String field;
#JsonProperty("id")
public Integer id;
}
Thanks a lot!
EDIT:
When I switch the JSON elements to dataA and dataB It works.
{
"ETag":"oiojojojojoj==",
"elements":[
{
"resource":"A",
"ETag":null,
"dataA":[
{
"anotherfield":"text",
"id":1,
"name":"GB"
}
]
},
{
"resource":"B",
"ETag":"dmVuZG9yOzIwMTctMDUtMDlUMTQ6MzE6NDNa",
"dataB":[
{
"field":"read",
"id":1
}
]
}
]
}
Element class:
public class Element {
#JsonProperty("resource")
public String resource;
#JsonProperty("ETag")
public Object eTag;
#JsonProperty("dataA")
public List<A> dataInA;
#JsonProperty("dataB")
public List<B> dataInB;
}

Spring Hateoas Resource List wihout content element tag

Regarding Spring HATEOAS Resource Wrapper vs ResourceSupport extention, i have:
public class FolderDto<T> implements Serializable {
private Number id;
private String name;
private final List<FolderDto<T>> folders;
private final List<T> items;
...
}
public class BoardDto implements Serializable {
private String name;
private Integer id;
private boolean shared;
...
}
#Test
public void test() throws Exception {
FolderDto<Resource<BoardDto>> folder = new FolderDto<Resource<BoardDto>>(0, null);
FolderDto<Resource<BoardDto>> child = new FolderDto<>(1000, "root");
folder.getFolders().add(child);
child.getItems().add(new Resource<>(new BoardDto(333,"whiteBoard", true)));
Resource<FolderDto<Resource<BoardDto>>> resource = new Resource<>(folder, linkTo(methodOn(BoardController.class).getBoards(null)).withSelfRel());
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(System.out, resource);
}
The result will be:
{
"id":0,
"nodes":[
{
"id":1000,
"name":"root",
"leaves":[
{
"content":{
"id":333,
"name":"whiteBoard",
"shared":true
},
"links":[{"rel":"board","href":"http://localhost/internal/boards/333"}]
}
]
}
],
"links":[{"rel":"self","href":"http://localhost/internal/boards"}]
}
How can i have this without the content element wrapper using Resource ?
In order to split responsibilities the rest controller logic (hateoas) entities should be as clean as possible and not have dependencies to other libs.
Desire result (obtained with extends ResourceSupport) :
{
"id":0,
"nodes":[
{
"id":1000,
"name":"root",
"leaves":[
{
"id":333,
"name":"whiteBoard",
"shared":true,
"links":[{"rel":"board","href":"http://localhost/internal/boards/333"}]
}
]
}
],
"links":[{"rel":"self","href":"http://localhost/internal/boards"}]
}

JSON data binding with custom logic using Jackson

I have defined JSON response which I want to deserialize into Java Objects. I managed to do it "manually" with the Tree Model but if possible I would like to use Data Binding instead. The problem is that I need some custom logic for some parts.
The JSON looks like this:
{
"resourcedescriptions": [
{
"path": "somePath",
"tag_pagetype": "default",
"tag_bookingcenter": [
"bc_ch",
"bc_de"
],
"resources": [
{
"path": "somePathDe.html",
"lang": "de",
"lastmodified": 1399020442914,
"mimetype": "text/html"
},
{
"path": "somePathEn.html",
"lang": "en",
"lastmodified": 1399907224208,
"mimetype": "text/html"
}
],
"lastmodified": 1399907224208
},
{
"path": "someOtherPath",
"tag_pagetype": "special",
"tag_bookingcenter": [
"bc_ch"
],
"resources": [
{
"path": "someOtherPathDe.html",
"lang": "de",
"lastmodified": 1399020442914,
"mimetype": "text/html"
},
{
"path": "someOtherPathEn.html",
"lang": "en",
"lastmodified": 1399907224208,
"mimetype": "text/html"
}
],
"lastmodified": 1399907224208
}
]
}
My Java Classes would be:
public class ResourceDescription {
private String path;
private LocalDateTime lastModified;
private String chartConfig;
private final List<Tag> tags = new ArrayList<Tag>();
private final List<Resource> resources = new ArrayList<Resource>();
}
public class Resource {
private String lang;
private String path;
private String mimeType;
private LocalDateTime lastModified;
}
public class Tag {
private String namespace;
private String name;
}
First question which I still don't fully understand even with reading many posts here. How do I deserialize this array of Resources from the JSON into my List of the ResourceDescription?
Second and most complex question. The JSON properties prefixed with "tag_" need to be transformed into the Tag class, whereas the the property name represents the namespace and the value (single or array) represent the name. So if the pattern is "namespace:name", the first ResourceDescription would have the following tags:
tag_pagetype:default
tag_bookingcenter:bc_ch
tag_bookingcenter:bc_de
Third the "lastmodified" should be transformed into DateTime from Joda-Time.
Is this even possible with data binding or should I stick to the Tree Model?
How do I deserialize this array of Resources from the JSON into my
List of the ResourceDescription?
You have to create additional root class which contains resourcedescriptions property. For example:
class Root {
private List<ResourceDescription> resourcedescriptions;
public List<ResourceDescription> getResourcedescriptions() {
return resourcedescriptions;
}
public void setResourcedescriptions(List<ResourceDescription> resourcedescriptions) {
this.resourcedescriptions = resourcedescriptions;
}
#Override
public String toString() {
return String.valueOf(resourcedescriptions);
}
}
The JSON properties prefixed with "tag_" need to be transformed into
the Tag class, whereas the the property name represents the namespace
and the value (single or array) represent the name.
You can handle this case using #JsonAnySetter annotation. You have to add new method to ResourceDescription class which could look like this:
#JsonAnySetter
public void setAnyValues(String propertyName, Object value) {
if (propertyName.startsWith("tag_")) {
if (value instanceof String) {
tags.add(new Tag(propertyName, value.toString()));
} else if (value instanceof List) {
List<?> values = (List<?>) value;
for (Object v : values) {
tags.add(new Tag(propertyName, v.toString()));
}
}
// throw exception?
} else {
// handle another unknown properties
}
}
Third the "lastmodified" should be transformed into DateTime from
Joda-Time.
You can handle JodaTime types by adding jackson-datatype-joda library. When you add it you can register JodaModule module.
mapper.registerModule(new JodaModule());
Additional problem that your JSON contain properties written using lowercase, but your POJO properties are written using camel-case. You can change JSON or POJO or use #JsonProperty("property-name-from-JSON") annotation or implement your own naming strategy. For example:
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PropertyNamingStrategyBase() {
#Override
public String translate(String propertyName) {
return propertyName.toLowerCase();
}
});
Full Java example how to you can deserialize your JSON:
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.datatype.joda.JodaModule;
public class JacksonProgram {
public static void main(String[] args) throws Exception {
String json = "{ ... }";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JodaModule());
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PropertyNamingStrategyBase() {
#Override
public String translate(String propertyName) {
return propertyName.toLowerCase();
}
});
System.out.println(mapper.readValue(json, Root.class));
}
}
class Root {
private List<ResourceDescription> resourcedescriptions;
public List<ResourceDescription> getResourcedescriptions() {
return resourcedescriptions;
}
public void setResourcedescriptions(List<ResourceDescription> resourcedescriptions) {
this.resourcedescriptions = resourcedescriptions;
}
#Override
public String toString() {
return String.valueOf(resourcedescriptions);
}
}
class ResourceDescription {
private String path;
private LocalDateTime lastModified;
private String chartConfig;
private final List<Tag> tags = new ArrayList<Tag>();
private final List<Resource> resources = new ArrayList<Resource>();
#JsonAnySetter
public void setAnyValues(String propertyName, Object value) {
if (propertyName.startsWith("tag_")) {
if (value instanceof String) {
tags.add(new Tag(propertyName, value.toString()));
} else if (value instanceof List) {
List<?> values = (List<?>) value;
for (Object v : values) {
tags.add(new Tag(propertyName, v.toString()));
}
}
// throw exception?
} else {
// handle another unknown properties
}
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public LocalDateTime getLastModified() {
return lastModified;
}
public void setLastModified(LocalDateTime lastModified) {
this.lastModified = lastModified;
}
public String getChartConfig() {
return chartConfig;
}
public void setChartConfig(String chartConfig) {
this.chartConfig = chartConfig;
}
public List<Tag> getTags() {
return tags;
}
public List<Resource> getResources() {
return resources;
}
#Override
public String toString() {
return "ResourceDescription [path=" + path + ", lastModified=" + lastModified
+ ", chartConfig=" + chartConfig + ", tags=" + tags + ", resources=" + resources
+ "]";
}
}
class Resource {
private String lang;
private String path;
private String mimeType;
private LocalDateTime lastModified;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public LocalDateTime getLastModified() {
return lastModified;
}
public void setLastModified(LocalDateTime lastModified) {
this.lastModified = lastModified;
}
#Override
public String toString() {
return "Resource [lang=" + lang + ", path=" + path + ", mimeType=" + mimeType
+ ", lastModified=" + lastModified + "]";
}
}
class Tag {
private String namespace;
private String name;
public Tag() {
}
public Tag(String namespace, String name) {
this.namespace = namespace;
this.name = name;
}
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Tag [namespace=" + namespace + ", name=" + name + "]";
}
}
Above program prints:
[ResourceDescription [path=somePath, lastModified=2014-05-12T17:07:04.208, chartConfig=null, tags=[Tag [namespace=tag_pagetype, name=default], Tag [namespace=tag_bookingcenter, name=bc_ch], Tag [namespace=tag_bookingcenter, name=bc_de]], resources=[Resource [lang=de, path=somePathDe.html, mimeType=text/html, lastModified=2014-05-02T10:47:22.914], Resource [lang=en, path=somePathEn.html, mimeType=text/html, lastModified=2014-05-12T17:07:04.208]]], ResourceDescription [path=someOtherPath, lastModified=2014-05-12T17:07:04.208, chartConfig=null, tags=[Tag [namespace=tag_pagetype, name=special], Tag [namespace=tag_bookingcenter, name=bc_ch]], resources=[Resource [lang=de, path=someOtherPathDe.html, mimeType=text/html, lastModified=2014-05-02T10:47:22.914], Resource [lang=en, path=someOtherPathEn.html, mimeType=text/html, lastModified=2014-05-12T17:07:04.208]]]]
You will need to create a custom deserializer for ResourceDescription in order to accomplish what you need to do. The syntax for specifying a custom deserializer for ResourceDescription will look like this:
#JsonDeserialize(using=ResourceDescriptionDeserializer.class)
public class ResourceDescription { ... }
This deserializer will have to iterate through each of the keys for each resource description to see if it begins with "tag_", strip off the prefix and use the remaining for the namespace and populate the name/value for the Tag before adding it to the array of the ResourceDescription being created.
For all other attributes/types I think you can just defer to the default deserialization and set those attributes on their respective fields.
Then, to deserialize the list of ResourceDescriptions you can specify a TypeReference to avoid writing a custom deserializer for ResourceDescriptions. The code will look something like this:
Map<String, List<ResourceDescription>> resultMap =
objectMapper.readValue(JSON, new TypeReference<Map<String, List<ResourceDescription>>>() {});
List<ResourceDescription> descriptions = resultMap.get("resourcedescriptions");
Here's an article that doesn't quite pair with what you're doing but I think will help with the general idea:
Using Jackson to deserialize array nested within array in JSON object

Categories