I am thinking of using Gson in my web-service, but i observed that Gson returns name of a variable in class as a key in Json format.
E.g
interface Animal{
}
class Dog implements Animal{
public String name, age;
#Override
public String toString() {
return name+"\t" + age;
}
}
Json for object of Dog is as follows:
{"name":"Tommy","age":"12"}
Now, my problem is since Key(name and age) in Json are dependent on name of variable, So if my variable name changes then key also changes. Then client for this web-service has to change the code whenever a variable name changes in web-service.
So Is their any way, so that i can map every variable with a key name. For e.g mapping name to nameOfDog and age to ageOfDog, so that json will look like
{"nameOfDog":"Tommy","ageOfDog":"12"}
Yes, using the SerializedName annotation.
Since is has a #Target(value=FIELD), it can (only) be applied to instance fields.
So:
#SerializedName("nameOfDog")
String name;
(in Jackson that would be #JsonProperty("nameOfDog"))
Related
I am creating a deserializer for schema.org entities with jackson library.
The schema.org entities are generated, so I can't add Jackson annotation on them.
Some fields accept several types.
An example is the address field (https://schema.org/address) of the Hotel entity (https://schema.org/Hotel) which accepts the PostalAddress and Text types.
The generated class for Hotel is the following (I only put the address field for simplicity):
#JsonLdTypeName("Hotel")
public class HotelImpl extends com.weedow.schemaorg.commons.model.JsonLdNodeImpl implements Hotel {
...
private Object address;
#Override
public <T> T getAddress() {
return (T) address;
}
#Override
public void setAddress(Text address) {
this.address = address;
}
#Override
public void setAddress(PostalAddress address) {
this.address = address;
}
...
}
Since the address field accepts 2 different types, the field type is Object.
When I deserialize a JSON, I don't use setters since Jackson doesn't know which one to choose and throws an exception: Conflicting setter definitions for property ...
I use the fields directly by adding the following 2 lines:
objectMapper.visibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
objectMapper.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
If the JSON is the following, it works fine because the address field is typed ("#type": "PostalAddress") and Jackson is able to construct the PostalAddress object and set the address field.
{
"#context" : "https://schema.org",
"#type" : "Hotel",
"address" : {
"#type" : "PostalAddress",
"addressLocality" : "Lost City",
"streetAddress" : "21 jump street"
}
}
If the JSON is the following, it doesn't work because there is no type (#type), but just a String that must be deserialized as a Text object.
(I have a TextDeserializer which works well if the field accepts only the Text type and which converts the String into a Text object)
{
"#context" : "https://schema.org",
"#type" : "Hotel",
"address" : "21 jump street, Lost City"
}
The address field being an object, Jackson set the String value directly on the field which is not correct, because it does not respect the accepted types.
I gave this example but the problem is equivalent with all fields that accept several types of which one of them is a DataType (https://schema.org/DataType).
I tried to implement my own AnnotationIntrospector to choose the right setter, but the resolveSetterConflict method is called before having the value.
public AnnotatedMethod resolveSetterConflict(MapperConfig<?> config, AnnotatedMethod setter1, AnnotatedMethod setter2) {
// Parsed value not available
}
Does Jackson allow a custom deserialization of a data according to
the value retrieved from the JSON, if this one is a scalar data
(String, number, boolean...)?
Does Jackson allow a custom deserialization of a data by calling the
right setter if there are several setters defined for the same field?
Does Jackson allow custom data deserialization if the field type is
Object, and the retrieved value is a scalar data (String, number,
boolean...)?
I have some json object that looks like this:
{
"make":"Volvo",
"model":"240",
"metadata":{
"color":"white",
"year":"1986",
"previousOwner":"Joe",
"condition":"good"
}
}
And I want to turn this JSON into List<Car>, which is comprised of the following objects:
public class Car {
private String make;
private String model;
private CarMetadata carMetadata;
}
public class CarMetadata {
private Body body;
private History history;
}
public class Body {
private String color;
private String condition;
}
public class History {
private String previousOwner;
private String year;
}
So essentially the point is that the object I want to turn it into (Car) is very nested, whereas my JSON is not very nested. In reality the "Car" object is actually much more nested than this example I'm showing.
I was thinking of two options:
Create a CarDTO object to represent my input JSON, do objectMapper.readValue(json, CarDTO.class), then map CarDTO to Car to create my List<Car>.
Just parse the JSON and create the final List<Car> object in the first place.
I don't want to create an unnecessary DTO, but I also don't want to mess with parsing this JSON.
Is there a best practice in this scenario, and would this even be a valid use of a DTO?
Use a DTO.
Although you can deserialize from json directly to your domain class, their structure differs so you would have to create a custom deserializer... DO NOT TRY THIS AT HOME. I've been there and it's completely not worth the hassle.
Use the DTO to parse the json into a POJO, then map the DTO to the domain object.
This will decouple the transport from your domain object, allowing both to change freely with only the mapping code being affected. It's also way easier to write, understand, test and debug.
While using jackson to deserialize and serialize a java DTO came across a weird behaviour.
Consider this sample java dto,
public class TempClz {
private List<String> field1;
public List<String> getField1() {
return field1;
}
public void setField1(List<String> field1) {
this.field1 = field1;
}
public List<String> getNotAtAll() {
return field1;
}
}
Now on creating a object of this class with field1 set to some value and on serializing this via a standard jackson mapper the sample json string obtained was
{"field1":["123"],"notAtAll":["123"]}
This behaviour was weird and I did not get a direct explanation for this in docs, but that once a getter is made, the property is available for both serialization and deserialization. This created a property using the function name in the resultant json response.
But again on deserialization of this string to pojo, the notAtAll list got appended to field1 list, i.e. in the resultant java object, field1 had size of two with values ["123","123"] which just seems wrong. Fixed this behaviour using jsonIgnore on the getter but can someone please help explain this behaviour exactly and whether this is intended?
A Getter Makes a Non-Public Field Serializable and Deserializable. So, no doubt that it added notAtAll field in json for getNotAtAll method. When you de-serialize the same string it has the values from both the getters but both of them return the same field i.e. field1. And consequently the values are added to your list. You've rightly used #JsonIgnore to ignore this getter.
There are several REST calls that require the same JSON entity with a different set of attributes. Example of the entity:
public class JsonEntity
{
public String id;
public String name;
public String type;
public String model;
}
JsonEntity is a part of the complex responses of different calls. The first call requires the whole JsonEntity without changes. Second call requires JsonEntity without type and model attributes. Thrid one requires JsonEntity without name attribute.
Is there any way to retrieve the same JSON entity with a particular set of attributes depending on the particular context (except separating JsonEntity) using Jackson?
I see 3 ways of doing this:
1. Use #JsonGetter
This annotation tells jackson to use a metho rather than a field for serialization.
Create 3 subclasses of JsonEntity, one for each response. Change JsonEntity and use #IgnoreField on every field, make them protected if possible. On each subclasses, create specific getter for the fields you need, example:
public class JsonEntitySecondCall extends JsonEntity
{
#JsonGetter("id")
public String getId(){
return id;
}
#JsonGetter("name")
public String getName(){
return name;
}
}
Also, create a clone/copy constructor for JsonEntity. For your second call, create a new JsonEntitySecondCall by cloning the original JsonEntity, and use it in your API. Because of the anotation, the created Object will only serialisze the given fields. I don't this you can just cast your object, because Jackson uses reflection.
2. Use #AnyGetter
the AnyGetter annotaiton allows you to define a map of what will be serialized:
private Map<String, Object> properties = new HashMap<>();
#JsonAnyGetter
public Map<String, Object> properties() {
return properties;
}
Now you just need to tell your JsonEntity what properties it needs to return before each call (you could create 3 methods, one for each context, and use an enum to set which one must be used.).
3. Use #JsonInclude(Include.NON_NULL)
This annotation tells Jackson not to serialize a field if it is null. You can then clone your object and set null the fields you don't want to send. (this only works if you shouldn't send null elements to the API)
For more on Jackson annotations use this link.
Is there knowing java reflection library which can get value of fields easily.
Ex .
If I have Address Object inside User Object. And Address object have city attribute.
public class Address {
private String city;
}
public class User {
private String name;
private Address address;
}
Then i want to pass address.city as parameter with User Object and i want to get city of user.
Is there any library which support my requirement.
Apache commons beanutils has
PropertyUtils.getNestedProperty(user, "address.city").
You can also do PropertyUtils.setNestedProperty(user, "address.city", "new city") but you need to make sure that address is not null.