When does #JsonProperty rename object field? - java

#Data
public class Tests {
#JsonProperty("comment")
private String notes;
}
I know how to use #JsonProperty to rename field as another name, but when does it rename the object field? For example, notes is renamed to comment.
I try to read the source code and find that there is some relative code in com.fasterxml.jackson.databind.ser.std.BeanSerializerBase#serializeFields and com.fasterxml.jackson.databind.ser.BeanPropertyWriter#serializeAsField. But the field has already been renamed as comment. So where does #JsonProperty rename object field?

Property name resolving happens in com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector
Have a look at this fragment:
public PropertyName findNameForDeserialization(Annotated a) {
...
// Get JsonProperty value for the field
JsonProperty pann = (JsonProperty)this._findAnnotation(a, JsonProperty.class);
if (pann != null) {
// here we are !!!
return PropertyName.construct(pann.value());
} else {
return !useDefault && !this._hasOneOf(a, ANNOTATIONS_TO_INFER_DESER) ? null : PropertyName.USE_DEFAULT;
}
}
All it happens in POJOPropertiesCollector.collectAll()

I created a simple Pojo class
public class JsonTest {
#JsonProperty("greetings")
String hello;
}
and run ObjectMapper#writeValueAsString method. After debugging for a while, I found that Jackson renames property name in the following method.
com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector#_renameProperties

Related

Handle Json non existent keys spring boot

I am creating a requestModel and let say a person doesn't send me some keys.
If that key is not present I want to put null if i get the value of the key.
I don't want to investigate if a key is present or not .
public class CustomerModel {
private Optional<String> s3Bucket;
private Optional<String> docType;
public String getS3Bucket() {
if(s3Bucket.isPresent()) {
return s3Bucket.get();
} else {
return null;
}
}
public void setS3Bucket(Optional<String> s3Bucket) {
this.s3Bucket = s3Bucket;
}
public Optional<String> getDocType() {
return docType;
}
public void setDocType(Optional<String> docType) {
this.docType = docType;
}
}
Do we have any library or something where.
1. If i get the key and it is not present in the coming request json, i will get the null out of it and if the key is present and has value . It will be stored as value.
2. When writing the getter for s3bucket (getS3Bucket), i dont want to write it for everykey value. Is there a automatic way to do this.
I looked at lot of posts but the scenario is not there.
P.S - I am new to java
I believe Jackson is exactly what you need. And if you are using Spring - it already uses Jackson under the hood I guess.
Here you can find some examples and documentation of how JSON mapping on to model class is done.
If you need to customize some behavior, you can use annotations like #JsonProperty (there are many).
If properties in your model class have the same names as properties in JSON, most probably you won't need to provide any further configs.
Here is a simple example:
public class User {
#JsonProperty("userName")
private String name;
private int age;
// getters and setters
}
And if you have JSON like this:
{
"userName" : "Foo Bar",
"age" : 18
}
Jackson will do all the magic for you unless you need something very specific.
If something is not in JSON you get (let's say you received JSON without age) - corresponding property in model class will be null if it is object type and default value (0, false, etc.) for primitives (in our case age would be 0).

Escape " (doublequote) in #Value property name

I have the following structure for a property:
my {
property {
item {
"1" {
value="some value"
}
"2" {
value="another value"
}
}
}
}
How do you refer to a property called "1" using the #Value annotation?
The example I have doesn't work. I tried the following options:
#Bean(name = "myProperty")
public String myProperty(#Value("${my.property.item.\"1\".value}") String myProperty) {
return myProperty;
}
#Bean(name = "myProperty")
public String myProperty(#Value("${my.property.item.'1'.value}") String myProperty) {
return myProperty;
}
#Bean(name = "myProperty")
public String myProperty(#Value("${my.property.item.1.value}") String myProperty) {
return myProperty;
}
None of which work.
Any advice appreciated!
Best way to fix that, is to just rename the properties, since java doesn't like when there are quotes inside of names.
Okay so Java definitely doesn't like the quotes in the name so my solution has instead (in my case) been to refer to the property using an alias although I realise this isn't apparent in the small example I gave (I didn't want to overcomplicate it).
In my example there's a set of property files that are imported into a Spring application.conf file.
Inside this application.conf file I put the following property:
myProperty.value=true
This overrides the other property
Then inside my original property file I simply refer to it.
my {
property {
item {
"1" {
value=${myProperty.value}
}
}
}
}
That means when I bind the property in a *Config.java file I can do:
#Bean(name = "myProperty")
public String myProperty(#Value("${myProperty.value}") String myProperty)
{
return myProperty;
}
Hope this helps people.

How to deserialise a subclass in Firebase using getValue(Subclass.class)

I'm using the new firebase sdk for android and use the real database feature. When i use the getValue(simple.class) everything is fine. But when i want to parse a class which is a subclass, all the attribute of the mother class are null, and i have this type of error:
No setter/field for name found on class uk.edume.edumeapp.TestChild
public class TestChild extends TestMother {
private String childAttribute;
public String getChildAttribute() {
return childAttribute;
}
}
public class TestMother {
protected String motherAttribute;
protected String getMotherAttribute() {
return motherAttribute;
}
}
this function
snapshot.getValue(TestChild.class);
motherAttribute attribute is null, and I get
No setter/field for motherAttribute found on class uk.edume.edumeapp.TestChild
the Json that i parse is:
{
"childAttribute" : "attribute in child class",
"motherAttribute" : "attribute in mother class"
}
Firebaser here
This is a known bug in some versions of the Firebase Database SDK for Android: our serializer/deserializer only considers properties/fields on the declared class.
Serialization of inherited properties from the base class, is missing in the in releases 9.0 to 9.6 (iirc) of the Firebase Database SDK for Android. It was added back in versions since then.
Workaround
In the meantime you can use Jackson (which the Firebase 2.x SDKs used under the hood) to make the inheritance model work.
Update: here's a snippet of how you can read from JSON into your TestChild:
public class TestParent {
protected String parentAttribute;
public String getParentAttribute() {
return parentAttribute;
}
}
public class TestChild extends TestParent {
private String childAttribute;
public String getChildAttribute() {
return childAttribute;
}
}
You'll note that I made getParentAttribute() public, because only public fields/getters are considered. With that change, this JSON:
{
"childAttribute" : "child",
"parentAttribute" : "parent"
}
Becomes readable with:
ObjectMapper mapper = new ObjectMapper();
GenericTypeIndicator<Map<String,Object>> indicator = new GenericTypeIndicator<Map<String, Object>>() {};
TestChild value = mapper.convertValue(dataSnapshot.getValue(indicator), TestChild.class);
The GenericTypeIndicator is a bit weird, but luckily it's a magic incantation that can be copy/pasted.
This was apparently finally fixed in release 9.6.
Fixed an issue where passing a derived class to DatabaseReference#setValue() did not correctly save the properties from the superclass.
for:
No setter/field for motherAttribute found on class uk.edume.edumeapp.TestChild
put setter for TestChild class:
public class TestMother {
private String motherAttribute;
public String getMotherAttribute() {
return motherAttribute;
}
//set
public void setMotherAttribute(String motherAttribute) {
this.motherAttribute= motherAttribute;
}
}
Check this https://firebase.google.com/support/guides/firebase-android
it says
"If there is an extra property in your JSON that is not in your Java class, you will see this warning in the log files: W/ClassMapper: No setter/field for ignoreThisProperty found on class com.firebase.migrationguide.ChatMessage
"
Blockquote
You can get rid of this warning by putting an #IgnoreExtraProperties annotation on your class. If you want Firebase Database to behave as it did in the 2.x SDK and throw an exception if there are unknown properties, you can put a #ThrowOnExtraProperties annotation on your class.
Blockquote

Extract annotation parameters from a bean/class

I am using the #JsonProperty(name = "property_name") (Jackson) annotation wherever the property name in the json is different from the property name in my object. Is there a way I can programmatically access this information somewhere else in code?
Example:
public class Entity {
protected long entityName;
#JsonProperty("entity_name")
public long getEntityName() {
return entityName;
}
public void setEntityName(String entityName) {
this.entityName = entityName;
}
}
What I need is a method to map entity_name to entityName. e.g, getActualFieldName("entity_name") should return "entityName". I can keep a separate file with all these mappings, but I don't want to do that because this information is already present in the form of #JsonProperty annotations. If I can extract it from there somehow, it would simplify things a bit.
Check this Helper utility. getAnnotationParameter

JsonMappingException: No suitable constructor found for type [simple type, class ]: can not instantiate from JSON object

I am getting the following error when trying to get a JSON request and process it:
org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type [simple type, class com.myweb.ApplesDO]: can not instantiate from JSON object (need to add/enable type information?)
Here is the JSON I am trying to send:
{
"applesDO" : [
{
"apple" : "Green Apple"
},
{
"apple" : "Red Apple"
}
]
}
In Controller, I have the following method signature:
#RequestMapping("showApples.do")
public String getApples(#RequestBody final AllApplesDO applesRequest){
// Method Code
}
AllApplesDO is a wrapper of ApplesDO :
public class AllApplesDO {
private List<ApplesDO> applesDO;
public List<ApplesDO> getApplesDO() {
return applesDO;
}
public void setApplesDO(List<ApplesDO> applesDO) {
this.applesDO = applesDO;
}
}
ApplesDO:
public class ApplesDO {
private String apple;
public String getApple() {
return apple;
}
public void setApple(String appl) {
this.apple = apple;
}
public ApplesDO(CustomType custom){
//constructor Code
}
}
I think that Jackson is unable to convert JSON into Java objects for subclasses. Please help with the configuration parameters for Jackson to convert JSON into Java Objects. I am using Spring Framework.
EDIT: Included the major bug that is causing this problem in the above sample class - Please look accepted answer for solution.
So, finally I realized what the problem is. It is not a Jackson configuration issue as I doubted.
Actually the problem was in ApplesDO Class:
public class ApplesDO {
private String apple;
public String getApple() {
return apple;
}
public void setApple(String apple) {
this.apple = apple;
}
public ApplesDO(CustomType custom) {
//constructor Code
}
}
There was a custom constructor defined for the class making it the default constructor. Introducing a dummy constructor has made the error to go away:
public class ApplesDO {
private String apple;
public String getApple() {
return apple;
}
public void setApple(String apple) {
this.apple = apple;
}
public ApplesDO(CustomType custom) {
//constructor Code
}
//Introducing the dummy constructor
public ApplesDO() {
}
}
This happens for these reasons:
your inner class should be defined as static
private static class Condition { //jackson specific
}
It might be that you got no default constructor in your class (UPDATE: This seems not to be the case)
private static class Condition {
private Long id;
public Condition() {
}
// Setters and Getters
}
It could be your Setters are not defined properly or are not visible (e.g. private setter)
I would like to add another solution to this that does not require a dummy constructor. Since dummy constructors are a bit messy and subsequently confusing. We can provide a safe constructor and by annotating the constructor arguments we allow jackson to determine the mapping between constructor parameter and field.
so the following will also work. Note the string inside the annotation must match the field name.
import com.fasterxml.jackson.annotation.JsonProperty;
public class ApplesDO {
private String apple;
public String getApple() {
return apple;
}
public void setApple(String apple) {
this.apple = apple;
}
public ApplesDO(CustomType custom){
//constructor Code
}
public ApplesDO(#JsonProperty("apple")String apple) {
}
}
When I ran into this problem, it was a result of trying to use an inner class to serve as the DO. Construction of the inner class (silently) required an instance of the enclosing class -- which wasn't available to Jackson.
In this case, moving the inner class to its own .java file fixed the problem.
Generally, this error comes because we don’t make default constructor.
But in my case:
The issue was coming only due to I have made used object class inside parent class.
This has wasted my whole day.
Thumb Rule: Add a default constructor for each class you used as a mapping class. You missed this and issue arise!
Simply add default constructor and it should work.
Can you please test this structure. If I remember correct you can use it this way:
{
"applesRequest": {
"applesDO": [
{
"apple": "Green Apple"
},
{
"apple": "Red Apple"
}
]
}
}
Second, please add default constructor to each class it also might help.
You have to create dummy empty constructor in our model class.So while mapping json, it set by setter method.
Regarding the last publication I had the same problem where using Lombok 1.18.* generated the problem.
My solution was to add #NoArgsConstructor (constructor without parameters), since #Data includes by default #RequiredArgsConstructor (Constructor with parameters).
lombok Documentation
https://projectlombok.org/features/all
That would solve the problem:
package example.counter;
import javax.validation.constraints.NotNull;
import lombok.Data;
#Data
#NoArgsConstructor
public class CounterRequest {
#NotNull
private final Integer int1;
#NotNull
private final Integer int2;
}
If you start annotating constructor, you must annotate all fields.
Notice, my Staff.name field is mapped to "ANOTHER_NAME" in JSON string.
String jsonInString="{\"ANOTHER_NAME\":\"John\",\"age\":\"17\"}";
ObjectMapper mapper = new ObjectMapper();
Staff obj = mapper.readValue(jsonInString, Staff.class);
// print to screen
public static class Staff {
public String name;
public Integer age;
public Staff() {
}
//#JsonCreator - don't need this
public Staff(#JsonProperty("ANOTHER_NAME") String n,#JsonProperty("age") Integer a) {
name=n;age=a;
}
}
You must realize what options Jackson has available for deserialization. In Java, method argument names are not present in the compiled code. That's why Jackson can't generally use constructors to create a well-defined object with everything already set.
So, if there is an empty constructor and there are also setters, it uses the empty constructor and setters. If there are no setters, some dark magic (reflections) is used to do it.
If you want to use a constructor with Jackson, you must use the annotations as mentioned by #PiersyP in his answer. You can also use a builder pattern. If you encounter some exceptions, good luck. Error handling in Jackson sucks big time, it's hard to understand that gibberish in error messages.
Add default constructors to all the entity classes
Failing custom jackson Serializers/Deserializers could also be the problem. Though it's not your case, it's worth mentioning.
I faced the same exception and that was the case.
For me, this used to work, but upgrading libraries caused this issue to appear. Problem was having a class like this:
package example.counter;
import javax.validation.constraints.NotNull;
import lombok.Data;
#Data
public class CounterRequest {
#NotNull
private final Integer int1;
#NotNull
private final Integer int2;
}
Using lombok:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
</dependency>
Falling back to
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
Fixed the issue. Not sure why, but wanted to document it for future.

Categories

Resources