I have a json like -
{
"type" : "employee",
"details" : {
"name" : "ABC",
"age" : 12,
"sex" : "male"
}
}
And a Java Class like -
public class Person {
String name;
String sex;
String type;
int age;
----getters and setters
}
I was wondering is there a ways to directly map the attributes of the details object to the person class like details.name to Person.name.
I know this can be achieved with custom deserializers, but I was hoping to avoid it. May be some annotations that GSON or Jackson provides.
There are a few ways to solve this, but what I would do is create the following class:
public class PersonWrapper {
private String type;
#JsonProperty("details")
private Person person;
}
EDIT:
If you don't want to add a wrapper class, you can try adding #JsonRootName(value = "details") to your Person class.
you can use #JsonProperties for mapping
Related
I have a json string that I would like to map to my java object. I am currently using gson to do so. The problem is however, I have setup part of my POJO to contain an abstract class. How can I map the json that corresponds to this abstract class correctly?
To clarify:
Here is an example of a json string I am currently receiving:
{
"Items" : [
{
"id" : "ID1",
"seller_id": 17,
"item_plan": {
"action" : "Sell"
}
},
{
"id" : "ID2",
"seller_id": 27,
"item_plan": {
"action": "Remove",
}
}
]
}
My request object is setup like so:
public class RequestObject {
#SerializedName("Items")
#Expose
private List<Item> items = null;
public class Item {
#SerializedName("id")
#Expose
private String id;
#SerializedName("seller_id")
#Expose
private Integer sellerID;
#SerializedName("item_Plan")
#Expose
private ItemPlan item_plan;
public abstract class ItemPlan {
#SerializedName("action")
#Expose
private String action;
public abstract void executePlan()
As you can see, my request object has an abstract class that represents item_plan. The idea here is that item_plan actions will have their own way of execution and therefore have a parent class called ItemPlan where each child class would represent the possible action plans and their own executionPlan ie. (SellPlan is a child class of ItemPlan, where SellPlan has its own implementation of the function executionPlan()).
How can I map my example json string to the following Java classes?
I have tried the following:
RuntimeTypeAdapterFactory<ItemPlan> itemPlanRuntimeTypeAdapterFactory =
RuntimeTypeAdapterFactory
.of(ItemPlan.class, "action")
.registerSubtype(SellPlan.class, "Sell")
.registerSubtype(RemovePlan.class, "Remove");
Gson gson = new
GsonBuilder().registerTypeAdapterFactory(itemPlanRuntimeTypeAdapterFactory).create();
RequestObject request = gson.fromJson(jsonString, RequestObject.class);
This, however, does not work. It is able to map everything I need but it fails to create the correctly create the abstracted class objects ie. while it will create the corresponding child objects (SellPlan for Sell and RemovePlan for Remove), it will make the action string of those classes null. There is a workaround where I can simply set the action string manually in the constructor of these classes but I would rather not. Is there a way to fix this?
Thank you.
You probably have to use the RuntimeTypeAdapterFactory.of overload with the additional maintainType parameter for which you then pass true as value. Otherwise, as you have noticed, Gson removes the type field value during serialization, and therefore the field keeps its default value null.
To explain my issue, let's say that I'm retrieving the following OData V2 Entity:
{
"d": {
"EmployeeID": 1,
"LastName": "Davolio",
"FirstName": "Nancy",
"Orders": {
"results": [
{
"OrderID": 10258
}
]
},
"Territories": {
"results": [
{
"TerritoryID": "06897"
}
]
}
}
}
And I have the corresponding model Class:
#JsonRootName(value = "d")
public class Employee {
#JsonProperty("EmployeeID")
private int employeeId;
#JsonProperty("LastName")
private String lastName;
#JsonProperty("FirstName")
private String firstName;
#JsonProperty("Orders")
private List<Order> orders;
#JsonProperty("Territories")
private List<Territory> territories;
...
}
As expected the exception com.fasterxml.jackson.databind.exc.MismatchedInputException is being triggered because the "Orders" and "Territories" collections are actually within the property "results", as the OData V2 states.
Do you guys have any idea how to ignore the nested "results" property and get the lists straight away?
Is a custom deserializer or wrapper class really needed in this case?
Thanks!
There are always multiple ways to tackle this problem.
One is to create a wrapper class and have Employee Directly reference it.
For example:
public class WrapperDto<T> implements Serializable {
#JsonProperty("results")
private List<T> elements;
}
public class Employee {
...
#JsonProperty("Orders")
private WrapperDto<Order> orders;
#JsonProperty("Territories")
private WrapperDto<Territory> territories;
...
}
when you want to get the orders, you have to call orders.getResults() to get the List
another solution can be found here where you have a custom wrapper:
How to map a nested value to a property using Jackson annotations?
I am creating a new endpoint in springboot that will return simple stats on users generated from an aggregate query in a mongo database. However I get a PropertyReferenceException. I have read multiple stackoverflow questions about it, but didn't find one that solved this problem.
We have a mongo data scheme like this:
{
"_id" : ObjectId("5d795993288c3831c8dffe60"),
"user" : "000001",
"name" : "test",
"attributes" : {
"brand" : "Chrome",
"language" : "English" }
}
The database is filled with multiple users and we want using Springboot aggregate the stats of users per brand. There could be any number of attributes in the attributes object.
Here is the aggregation we are doing
Aggregation agg = newAggregation(
group("attributes.brand").count().as("number"),
project("number").and("type").previousOperation()
);
AggregationResults<Stats> groupResults
= mongoTemplate.aggregate(agg, Profile.class, Stats.class);
return groupResults.getMappedResults();
Which produces this mongo query which works:
> db.collection.aggregate([
{ "$group" : { "_id" : "$attributes.brand" , "number" : { "$sum" : 1}}} ,
{ "$project" : { "number" : 1 , "_id" : 0 , "type" : "$_id"}} ])
{ "number" : 4, "type" : "Chrome" }
{ "number" : 2, "type" : "Firefox" }
However when running a simple integration test we get this error:
org.springframework.data.mapping.PropertyReferenceException: No property brand found for type String! Traversed path: Profile.attributes.
From what I understand, it seems that since attributes is a Map<String, String> there might be a schematic problem. And in the mean time I can't modify the Profile object.
Is there something I am missing in the aggregation, or anything I could change in my Stats object?
For reference, here are the data models we're using, to work with JSON and jackson.
The Stats data model:
#Document
public class Stats {
#JsonProperty
private String type;
#JsonProperty
private int number;
public Stats() {}
/* ... */
}
The Profile data model:
#Document
public class Profiles {
#NotNull
#JsonProperty
private String user;
#NotNull
#JsonProperty
private String name;
#JsonProperty
private Map<String, String> attributes = new HashMap<>();
public Stats() {}
/* ... */
}
I found a solution, which was a combination of two problems:
The PropertyReferenceException was indeed caused because attributes is a Map<String, String> which means there is no schemes for Mongo.
The error message No property brand found for type String! Traversed path: Profile.attributes. means that the Map object doesn't have a brand property in it.
In order to fix that without touching my orginal Profile class, I had to create a new custom class which would map the attributes to an attributes object having the properties I want to aggreate on like:
public class StatsAttributes {
#JsonProperty
private String brand;
#JsonProperty
private String language;
public StatsAttributes() {}
/* ... */
}
Then I created a custom StatsProfile which would leverage my StatsAttributes and would be similar to the the original Profile object without modifying it.
#Document
public class StatsProfile {
#JsonProperty
private String user;
#JsonProperty
private StatsAttributes attributes;
public StatsProfile() {}
/* ... */
}
With that I made disapear my problem with the PropertyReferenceException using my new class StatsAggregation in the aggregation:
AggregationResults<Stats> groupResults
= mongoTemplate.aggregate(agg, StatsProfile.class, Stats.class);
However I would not get any results. It seems the query would not find any document in the database. That's where I realied that production mongo objects had the field "_class: com.company.dao.model.Profile" which was tied to the Profile object.
After some research, for the new StatsProfile to work it would need to be a #TypeAlias("Profile"). After looking around, I found that I also needed to precise a collection name which would lead to:
#Document(collection = "profile")
#TypeAlias("Profile")
public class StatsProfile {
/* ... */
}
And with all that, finally it worked!
I suppose that's not the prettiest solution, I wish I would not need to create a new Profile object and just consider the attributes as a StatsAttributes.class somehow in the mongoTemplate query. If anyone knows how to, please share 🙏
Given the follwing POJO:
class A {
private String name;
private String desc;
private List<A> subclasses;
}
I would produce that kind of json, by excluding a the field desc` from the subclass :
{
name : "aname"
desc: "adesc",
subclasses : [{
name : "aname"
},{
name : "anotherame"
}]
}
Or a field from the parent class and not from the child class
To exclude a field use a #JsonIgnore annotation. Look more over here -
http://forum.springsource.org/showthread.php?92684-Exclude-bean-field-from-JSON-response
and here -
http://jackson.codehaus.org/1.0.0/javadoc/org/codehaus/jackson/annotate/JsonIgnore.html
I have the following json file:
{
"segments": {
"externalId": 123,
"name": "Tomas Zulberti",
"shouldInform": true,
"id": 4
}
}
But the java model is as follows:
public class Segment {
private String id;
private String name;
private boolean shouldInform;
// getter and setters here...
}
When Jackson is parsing it raises an exception becuase there is no getter or setter for the field "externalId". It there a decorator that can be used to ignore a json field?
You can use annotation #JsonIgnoreProperties; if it's just one value you want to skip, something like:
#JsonIgnoreProperties({"externalId"})
or to ignore anything that can't be used:
#JsonIgnoreProperties(ignoreUnknown=true)
There are other ways to do it too, for rest check out FasterXML Jackson wiki.
Also we can use mapper.enable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
instead #JsonIgnoreProperties(ignoreUnknown=true)
but for particular property we can use
#JsonIgnoreProperties({"externalId"})
public class Segment {
private String id;
private String name;
private boolean shouldInform;
// getter and setters here...
}