I have a class which looks like this :
public class Item {
private ItemHeader header;
private ItemBody body;
}
public class ItemHeader {
private int id;
private String name;
}
public class ItemBody {
private List<String> values;
private List<String> details;
}
The fields of ItemHeader and ItemBody are accessible via setters and getters from the Item class as well as from their corresponding classes. All said setters and getters EXCEPT get/setItemBody +get/setItemHeader are annotated with #JsonIgnore.
When Item instance is returned by a GET REST method, the Response looks as following :
{
"body": {
"details":[]
"values":[]
},
"header": {
"id": 145,
"name": "name_of_item",
},
"details":[],
"values":[],
"id": 145,
"name": "name_of_item"
}
Internals of itemHeader and itemBody are spilled twice into the deserialized Json , once (correctly) inside the corresponding fields and the second time just outside them.
I do not have much control over Jackson definitions behind the scenes and can basically only control my class hierarchy with annotations and such.
Please advise - how to remove the duplication, the "spilled over" values...
I've ended up removing the double layer of getters/setters which solved the issue, and in the process discovered that indeed, the serialization path and the de- path used different libraries . So https://stackoverflow.com/users/2513573/adamskywalker had the right idea
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?
Small question regarding Spring Webflux, and how to get the nested List of Pojo that is present in a http response directly.
We are consuming an API which response is something like
{
"noNeedThisField": "I do not need this",
"listOfWhatIwant": [
{
"personName": "Alice",
"personAge": "11"
},
{
"personName": "Bob",
"personAge": "22"
},
{
"personName": "Charlie",
"personAge": "33"
}
],
"uselessField": "This is useless",
"manyFieldsNoNeed": "it is one response, which contains a lot of fields that I do not need, I just need to retrieve the list DIRECTLY please",
"noNeed": true,
"anotherNotImportant": "this is not important at all"
}
Basically, it is one response, which contains a lot of fields I do not need, plus an element of type list in it, which I would like to get directly.
If I create two different classes, first one
public class PojoWithListAndOtherNoNeedFields {
private String noNeedThisField;
private List<MyNestedPojo> listOfWhatIwant;
private String uselessField;
private String manyFieldsNoNeed;
private boolean noNeed;
private String anotherNotImportant;
}
//getters setters
second one
public class MyNestedPojo {
private String personName;
private String personAge;
//getters setters
}
And invokes Webclient like this:
public Mono<PojoWithListAndOtherNoNeedFields> sendReqest() {
return webClient.mutate().baseUrl("url").build().post().uri("/route").retrieve().bodyToMono(PojoWithListAndOtherNoNeedFields.class);
}
It is working fine! I just need to carry a very large class that I do not need in my code, and retrieve the nested list of what I need with a getter each time.
However, I was wondering is it is possible to do something similar as (this is not working)
public Mono<List<MyNestedPojo>> sendReqest() {
return webClient.mutate().baseUrl("url").build().post().uri("/route").retrieve().bodyToMono(List<MyNestedPojo>.class);
}
In order to retrieve the nested element directly.
My goal is to get rid of PojoWithListAndOtherNoNeedFields entirely, and getting the List< MyNestedPojo> directly. Is it possible?
How to perform this in a proper way in Spring using the Webclient please?
Thank you
You can use the #JsonIgnoreProperties annotation to inform the ObjectMapper to ignore any fields not included in your POJO when deserialisating from json to a POJO.
#JsonIgnoreProperties(ignoreUnknown = true)
public class PojoWithListAndOtherNoNeedFields {
private List<MyNestedPojo> listOfWhatIwant;
}
public class MyNestedPojo {
private String personName;
private String personAge;
}
JavaDocs
In response to api call, i'm sending Json Class Object as response.
I need response like this without empty objects being removed.
{
"links": {
"products": [],
"packages": []
},
"embedded":{
"products": [],
"packages": []
}
}
but final Response is looking like this
{
"links": {},
"embedded": {}
}
Two things to be aware of:
null and empty are different things.
AFAIK Jackson is configured to serialize properties with null values by default.
Make sure to properly initialize your properties in your object. For example:
class Dto {
private Link link;
private Embedded embedded;
//constructor, getters and setters...
}
class Link {
//by default these will be empty instead of null
private List<Product> products = new ArrayList<>();
private List<Package> packages = new ArrayList<>();
//constructor, getters and setters...
}
Make sure your classes are not extending another class with this annotation #JsonInclude(JsonInclude.Include.NON_NULL). Example:
//It tells Jackson to exclude any property with null values from being serialized
#JsonInclude(JsonInclude.Include.NON_NULL)
class BaseClass {
}
//Any property with null value will follow the rules stated in BaseClass
class Dto extends BaseClass {
private Link link;
private Embedded embedded;
//constructor, getters and setters...
}
class Link extends BaseClass {
/* rest of the design */
}
If you have the latter and you cannot edit BaseClass then you can define different rules in the specific classes:
class Link extends BaseClass{
//no matter what rules are defined elsewhere, this field will be serialized
#JsonInclude(JsonInclude.Include.ALWAYS)
private List<Product> products;
//same here
#JsonInclude(JsonInclude.Include.ALWAYS)
private List<Package> packages;
//constructor, getters and setters...
}
When I execute request to db
db.users.find({"name": "Max"})
I get this result
{"_id":ObjectId("5785718ee271a7c7ebaad28b"),"name":"Max","visits-by-day":[{"day":"Thursday","visitsAmount":20},{"day":"Saturday","visitsAmount":4}]}
JSON structure example:
{
"users": [
{
"name": "Bobby",
"visits-by-day": [
{
"day": "Sunday",
"visitsAmount": 8
},
{
"day": "Monday",
"visitsAmount": 3
}
]
}
]
}
Here my Java code
MongoUser user = mongoTemplate.findOne(query(where("name").is("Max")), MongoUser.class);
The model
#Document
public class MongoUser {
#Id
private String id;
private String name;
private List<VisitsPerDay> visitsByDay;
// Getters & Setters omitted
}
public class VisitsPerDay {
private String day;
private Integer visitsAmount;
// Getters & Setters omitted
}
Why Spring does return a null empty instead of serialized Java object?
By default, the collection queried for a given typed is derived from the simple name of the domain type you want to read. In your case, that would be mongoUser. To get your example to work, you basically have two options:
Explicitly configure the collectionName in the #Document annotation on MongoUser to users. That will basically tie instances of that class to that collection and let all data access operations for that class to work with that collection (e.g. for repositories etc.).
When calling MongoTemplate, use the overload of findOne(…) that takes an explicit collection name:
template.findOne(query(…), MongoUser.class, "users");