I've used Jackson for years, and I am not sure I ever faced this issue.
Using Jackson 2.12.5 in a Spring Boot 2.5.5 project, I have an object that I need to serialize. I have no issue with other fields, but these 2 fields are causing me problems :
#Jacksonized
#Builder
#Getter
public class ComputationResult {
private final String pId;
private final String cId;
... other fields ignored
}
As you can see, I am also using Lombok annotations. When "delombokized", the getters are :
public String getPId() {
return this.pId;
}
public String getCId() {
return this.cId;
}
when serializing the POJO, I expect the field names to be "pId" and "cId", but they are not : I get "pid" and "cid", all lower-case. I don't have the problem with other fields, for which the case is respected.
It caused me an issue because I need to serialize then deserialize the POJO, and the deserialization failed because it could not map "cid" json field to "cId" java field.
There are various workarounds (I am using #JsonAlias("cid") on the field to allow the deserialization), but I am puzzled : is this an expected behavior by Jackson ? does it process String fields differently depending on their length ? or is it a java beans convention that I am not aware of ?
Is there a property to set in the objectMapper to "fix" the behavior, without implementing my own com.fasterxml.jackson.databind.PropertyNamingStrategy ?
The problem seems to be caused by how JavaBeans methods get generated when there's a single lowercase character at the beginning of the property name. You might be surprised by the fact that getpId and getcId are indeed correctly named, just as I was.
In short, pId correctly results in the getter getpId rather than the Lombok-generated getPId (the one JavaBeans should have kept, in my opinion).
Now, the interesting part is that Jackson makes cid and pid out of getCId and getPId, respectively, for some reason... while at the same time producing cId and pId for getcId and getpId.
So while getcId and getpId are a quirk of JavaBeans, it seems that Jackson is behaving correctly by default, when the getters are correctly named, i.e., getpId -> "pId" and getcId -> "cId".
Given that Lombok generates getPId and getCId, which lead to the all-lowercase keys in the resulting JSON, deserialization does not work.
If you don't like the getpId/getcId naming, then you may have to write your own getters and force a property name explicitly:
#Builder
#Jacksonized
class ComputationResult {
private final String pId;
private final String cId;
#JsonProperty("pId")
public String getPId() {
return pId;
}
#JsonProperty("cId")
public String getCId() {
return cId;
}
}
For the setters, you already have to rely on #Jacksonized because of final fields anyway, so it doesn't make a difference. Just note that #Getter has to be omitted because it'd result in duplicate properties (with the other kind of naming, that is).
Related
Hi StackOverflow Community,
I am currently trying to deserialize JSON request bodies provided via Spring Boot #RestController.
The request body contains the following array:
{
...
"productIds": [
"123abc",
"234def"
],
...
}
However, I don't want to deserialize the product IDs into a list of Strings, but rather use a simple wrapper class (for various reasons, including but not limited to additional type safety and validation opportunities). Consequently the class looks like this (Lombok annotations were used to keep the code snippet short):
#Value
#AllArgsConstructor
public class TheRequest {
...
List<ProductId> productIds;
...
}
with ProductId being just a simple wrapper as already said (validation annotations are omitted for the sake of brevity):
#Value
#AllArgsConstructor
public class ProductId{
String id;
}
Looking at Stackoverflow I only found ways to achieve this using rather verbose custom deserialization methods.
However, I am a bit astonished, that Jackson does not provide this functionality out of the box. Consequently it would be great if anyone has any idea if
there is a more elegant way to achieve deserialization of a array of Strings into a List of WrapperObjects, ideally only using Jackson annotations?
there is an elegant way to achieve serialization of such a resulting List of ProductId wrapper objects back into String objects, ideally also using only Jackson annotations? I tried Jacksons #Value but that did not provide the required result.
To me still to verbose but it seems to be a working solution with Jacson 2.14+:
public record PayloadId(String id) {
#JsonCreator(mode = Mode.DELEGATING)
public PayloadId{}
#JsonValue
#Override
public String id() {
return id;
}
}
...and here is the records test https://github.com/FasterXML/jackson-databind/blob/2.14/src/test-jdk14/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java
Specifically I am wondering how when deserializing an object the deserializer could set a private field? Thinking of an example class like this:
public class MyClass {
#JsonProperty( "My String" );
private String myString;
}
If this is deserialized using objectMapper.readValue(json, MyClass.class); how does the resulting object have this field set if it is marked as private?
Calling Field.setAccessible(true) before reading or writing a value through reflection does the trick here.
For details see the corresponding javadoc: https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/AccessibleObject.html#setAccessible-boolean-
But use with care ;-)
Quite a few frameworks allow access to private fields in this manner by using Field.setAccessible(true). It allows the application to ignore the Java language visibility rules (i.e. private) and read or change the value via an instance of the Reflection API Field class.
A little more can be found in this question:
Java reflection - impact of setAccessible(true)
The short answer is that it can't normally. We use lombok to generate the getter/setter for the variables, but you can of course write your own. Jackson has the same visibility as your main code does, so a private field cannot be mapped without some public getter/setter OR configuring the object mapper like so... objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);.
It wouldn't be able to serialize that either normally. You can use Lombok #Getter and #Setter on the class level so Jackson can work with myString, or put #JsonAutoDetect(fieldVisibility = Visibility.ANY) at the class level like below.
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class MyClass {
#JsonProperty( "My String" );
private String myString;
}
I have a class that I want to deserialize and serialize with Jackson. Currently this is what I have
class Person {
#JsonAlias("fullName")
#JsonDeserialize(using = NameDeserializer.class)
String name;
}
I want to be able to read from {"fullName": "John Doe"} and write to {"name": "John Doe"}.
I am also using Lombok to create setter and getter, so I could not use #JsonProperty on method level. Any idea how would I approach this, because currently it seems like the #JsonAlias is not working as expected. I'm using Jackson 2.10 to map object.
EDIT
Turns out using Alias works just fine. Though in figuring out how it didn't work the first time, I override lombok setter
#JsonProperty("firstName")
public void setName(String name){
this.name = name;
}
From here I found out that my Deserializer is actually the problem.
Just remove the custom deserializer, and annotate the field with #JsonProperty, which should make your scenario work:
class Person {
#JsonAlias("fullName")
#JsonProperty
String name;
}
The custom deserializer controls how the deserialization is done, and this is where the alias is probably being ignored.
you can use JsonProperty annotation with properties as it is written here https://github.com/FasterXML/jackson-annotations
public class Name {
#JsonProperty("firstName")
public String _first_name;
}
There a json object to be sent to the server, which contains a field:
{"sName":"something"}
In my request model,I declare a var with the same name:
String sName;
But I got null when I receive in the Controller.
I change the field name to lower-case(sname) or add JsonProperty(value="sName") annotation,it work. So where is the problem?
Controller
public ResponseEntity<JSONObject> getComprehensiveInquiryCp(#Validated #RequestBody ComprehensiveInquiryRequestModel body) {
Map<String, Object> content;
JSONObject result = new JSONObject();
String sLicense = body.getSLicense();
...
}
ComprehensiveInquiryRequestModel
#Data
public class ComprehensiveInquiryRequestModel {
...
//#JsonProperty(value = "sLicense")
private String sLicense;
...
}
From top of my head: if you have accessors in that bean, then I think jackson prefers to use them if they exist. And/or Jackson prefers accessors for private fields. As you noticed you can alter that behaviour with Jakson configuration (for example via annotions).
Try:
1. to debug, remove accessor methods and make field public. If that works then change the field back to private and make sure accessor methods are named correctly.
Also single charater prefixes are not a good practise. They can be problematic and confusing. Prefixes in general are lazy and un-Clean Code(tm) practise.
It is important that your setters (and getters) are present and actually conform to the Java naming conventions. A json property named "myFirstName" usually requires a public setter "setMyFirstName(...)" for example. So "sName" needs "setSName()", I guess.
Sure sounds like the naming convnetion might be at fault here.
New to java and spring boot.
While trying to serialize the following class,
public class ActionItems {
private String APpID;
public String getAPpID() {
return APpID;
}
public void setAPpID(String aPpID) {
APpID = aPpID;
}
// other fields
}
got the json string as
{
"appID": null,
}
Whilst, cross checking the getter name with decapitilize(), it is matching with the field name.
Introspector.decapitalize("APpID") - gives "APpID"
Is jackson using a different set of rules and methods when generating the property name from the getter method?
PS: I am aware that, variable name should begin with small case. While going through the java beans naming convention spec got this question.
I am using jackson 2.9.3v.
PS: As per the link PropertyNamingStrategy, it should have produced APpID instead of appId right?
Could someone provide some input here?
Thanks.
In Jackson, you can custom PropertyNamingStrategy, and
In absence of a registered custom strategy, default Java property
naming strategy is used, which leaves field names as is, and removes
set/get/is prefix from methods (as well as lower-cases initial
sequence of capitalized characters).
Also, you can custom a property name like:
#JsonProperty("APpID") // produce {"APpID":"s"}
public String getAPpID() {
return APpID;
}