If I try to deserialize a JSON string that is missing a field from the object class, that field is set to null when the object is created. I need Jackson to throw an exception when this happens so I don't have to implement an assertNotNullFields() method in every object; i.e the JSON string input must have defined all the fields in the class or a JsonProcessingException is thrown.
Object mapper configuration:
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
JSON object:
public class LogInUser {
public String identifier, password;
public LogInUser() {}
}
Is this possible given my configuration?
You could try using JsonCreator within your LogInUser class as mentioned here. When introduced into the code you posed in your quesion, it would look something like this:
public class LogInUser {
#JsonCreator
public LogInUser(#JsonProperty(value = "identifier", required = true) String identifier, #JsonProperty(value = "password", required = true) String password) {
this.identifier = identifier;
this.password = password;
}
public String identifier, password;
public LogInUser() {}
}
When one or more of the values are null, it should throw an exception.
Related
I am trying to set null values to database of empty values from JSON payload. This problem caused beacause I have unique constraints on social entity fields.
I have request DTO that looks like:
#Value
#With
#Builder
#Jacksonized
public class AccountCreateRequest {
#NotBlank
String firstName;
String phone;
#Password
#NotBlank
String password;
#Email(message = "Email is not valid.")
#NotBlank
String email;
#NotBlank
String role;
SocialDto social;
}
Nested social DTO looks like
#Value
#Builder
#Jacksonized
public class SocialDto {
#JsonInclude(JsonInclude.Include.NON_EMPTY)
String telegramId;
#JsonInclude(JsonInclude.Include.NON_EMPTY)
String linkedinLink;
#JsonInclude(JsonInclude.Include.NON_EMPTY)
String githubLink;
}
Json example:
{
...fields...,
social: {
telegramId: "",
githubLink: "",
...
}
}
This JSON object is deserialized with social empty strings and doesn't ignore that values.
Moving annotation to the class level - didn't help for me.
How could I fix this problem?
#JsonInclude(JsonInclude.Include.NON_EMPTY) does not work for the intended purpose of treating empty strings as null on object deserialization. This annotation is for including fields in Object to JSON serialization.
You need to add a custom Deserializer like so: How to deserialize a blank JSON string value to null for java.lang.String?
In order to combine this with #Jacksonized, which already registers a #JsonDeserialize(using = x.class) on the class level, you can do the following:
public class JsonStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
JsonNode node = jsonParser.readValueAsTree();
String nodeText = node.asText();
// todo: isBlank or isEmpty depending on your needs
if (nodeText.isBlank()) {
return null;
}
return nodeText;
}
}
and parse the JSON object with the following code:
SimpleModule stringModule = new SimpleModule();
stringModule.addDeserializer(String.class, new JsonStringDeserializer());
ObjectMapper jsonObjectMapper = new ObjectMapper();
jsonObjectMapper.registerModule(stringModule);
jsonObjectMapper.readValue(input, modelClass);
Or you can add #JsonDeserialize(using = JsonStringDeserializer.class) on every String field that needs it if you don't control the ObjectMapper.
At the deserialization process (which as I understand is the process of converting JSON data into a Java Object), how can I tell Jackson that when it reads a object that contains no data, it should be ignored?
I'm using Jackson 2.6.6 and Spring 4.2.6
The JSON data received by my controller is as follows:
{
"id": 2,
"description": "A description",
"containedObject": {}
}
The problem is that the object "containedObject" is interpreted as is and it's being instantiated. Therefore, as soon as my controller reads this JSON data, it produces an instance of the ContainedObject object type but I need this to be null instead.
The easiest and fastest solution would be that in the JSON data received, this value be null like this:
{
"id": 2,
"description": "A description",
"containedObject": null
}
But this isn't possible since I'm not in control of the JSON data that is sent to me.
Is there an annotation (like this explained here) that works for the deserialization process and could be helpfull in my situation?
I leave a representation of my classes for more information:
My entity class is as follows:
public class Entity {
private long id;
private String description;
private ContainedObject containedObject;
//Contructor, getters and setters omitted
}
And my contained object class as follows:
public class ContainedObject {
private long contObjId;
private String aString;
//Contructor, getters and setters omitted
}
I would use a JsonDeserializer. Inspect the field in question, determine, if it is emtpy and return null, so your ContainedObject would be null.
Something like this (semi-pseudo):
public class MyDes extends JsonDeserializer<ContainedObject> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
//read the JsonNode and determine if it is empty JSON object
//and if so return null
if (node is empty....) {
return null;
}
return node;
}
}
then in your model:
public class Entity {
private long id;
private String description;
#JsonDeserialize(using = MyDes.class)
private ContainedObject containedObject;
//Contructor, getters and setters omitted
}
Hope this helps!
You can implement a custom deserializer as follows:
public class Entity {
private long id;
private String description;
#JsonDeserialize(using = EmptyToNullObject.class)
private ContainedObject containedObject;
//Contructor, getters and setters omitted
}
public class EmptyToNullObject extends JsonDeserializer<ContainedObject> {
public ContainedObject deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
long contObjId = (Long) ((LongNode) node.get("contObjId")).numberValue();
String aString = node.get("aString").asText();
if(aString.equals("") && contObjId == 0L) {
return null;
} else {
return new ContainedObject(contObjId, aString);
}
}
}
Approach 1 : This is mostly used. #JsonInclude is used to exclude properties with empty/null/default values.Use #JsonInclude(JsonInclude.Include.NON_NULL) or #JsonInclude(JsonInclude.Include.NON_EMPTY) as per your requirement.
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Employee {
private String empId;
private String firstName;
#JsonInclude(JsonInclude.Include.NON_NULL)
private String lastName;
private String address;
private String emailId;
}
More info about the jackson annotations : https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations
Approach 2 : GSON
use GSON (https://code.google.com/p/google-gson/)
I want to MAP my HTTP request parameter value directly to my DTO USING #JsonProperty on the basis of the variable name not by #JsonProperty value. I am not able to map the value to DTO because it's expecting request value according to the JsonProperty name. Is there anyway to disable #JsonProperty value while using the #RequestBody ?
JSON send by frontend:
{
"userId":"1",
"payMethod":"payMethod"
}
MyDto.class
public class MyDto{
#JsonProperty(value = user_id, required = true)
private String userId;
#JsonProperty(value = BETAALMETHODE, required = true)
private String payMethod;
//getter setter
}
MyController.class
public class MyController{
#RequestMapping(value = "payment", method = RequestMethod.PUT)
public Integer PaymentUpdate(#RequestBody final MyDto myDto) throws JsonProcessingException {
}
you can do this by using multiple setter method for that DTO method. For example
Payload:
{
"userId":"1",
"payMethod":"payMethod"
}
then
MyDto.class public class MyDto{
#JsonProperty(value = user_id, required = true)
private String userId;
#JsonProperty(value = BETAALMETHODE, required = true)
private String payMethod;
add one more setter relevant to the required variable name in the DTO class.
#JsonSetter("specifiedName")
void setUserId(String userId){
this.userId=userId
}
void setPayMethod(String payMethod){ // Will work for "BETAALMETHODE" variable name
this.payMethod=payMethod
}
#JsonSetter("payMethod")
void setPayMethod(String payMethod){
this.payMethod=payMethod
}
This will solve your problems and variable payMethod will assign in both the cases.
You can use JacksonMixin during csv parsing:
public abstract class MyDtoMixin {
#JsonProperty(value = user_id, required = true)
private String userId;
#JsonProperty(value = BETAALMETHODE, required = true)
private String payMethod;
}
ObjectMapper mapper = new ObjectMapper(); // or CsvMapper mapper = new CsvMapper();
mapper.addMixInAnnotations(MyDto.class, MyDtoMixin.class);
I'm still getting NPE while trying to deserialize an JSON. I wish to put a default value (0 / null) if property is missing. This is my Spring Boot configuration bean:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setObjectMapper(objectMapper());
return jsonConverter;
}
ObjectMapper objectMapper() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(ProductBasicDto.class, new ProductDeserializer());
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
mapper.registerModule(simpleModule);
return mapper;
}
My custom deserializer:
#Override
public ProductBasicDtoWrapper deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
System.out.println(node.get("id").asLong()); // here it throws NPE
return null; // ignore that, just for testing
}
json:
{
"name": "js",
"category": "CoNiFer"
}
and execption:
java.lang.NullPointerException: null
at api.product.infrastructure.ProductDeserializer.deserialize(ProductDeserializer.java:19)
at api.product.infrastructure.ProductDeserializer.deserialize(ProductDeserializer.java:14)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3072)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:235)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:223)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:206)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:157)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:130)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:124)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:131)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:870)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:881)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:855)
How can I avoid this NPE?
Is it possible to put 0 / null values without explict checks in deserializer if property(like id in this example) is missing?
EDIT: Added some code examples
Let's say this is my DTO class:
class MyDto implements Serializable {
private final String firstName;
private final String lastName;
}
Now I'm creating my custom mapper:
#Override
public ProductBasicDtoWrapper deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException,
JsonProcessingException {
objectMapper.convertValue(jsonNode, MyDto.class);
...
Now I decided to add additional Integer to MyDto:
class MyDto implements Serializable {
private final String firstName;
private final String lastName;
private final Integer age;
}
It's great it doesn't needs changing ANYTHING else (in my mapper class). But assume I got this kind of json
{
"firstName": "name"
}
Now it throws NPE. So the idea is to check if values are null in mapper. Let's do it:
#Override
public ProductBasicDtoWrapper deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException,
JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
node.get("firstName") == null ?
node.get("lastName") == null ?
node.get("age") == null ?
}
Okay it works, now let's say I added one more attribute to my DTO class
class MyDto implements Serializable {
private final String firstName;
private final String lastName;
private final Integer age;
private final String card;
}
The problem is, in this case I have to change ALSO my mapper class because 4th parameter isn't handled.
This line returns null
node.get("id") // returns null when property is not defined
Because the property is obviouly not defined in the JSON. You can resolve your problem in many ways, but you will always have to check null when using get(name) and one of the methods asLong, asString, etc, or just check if the property is defined with has(name).
You can also use a helper function
public static Long getLong(JsonNode node, String name) {
if (node.get(name) != null && node.get(name).isLong()) {
return node.get(name).asLong();
} else {
return null;
}
}
You can return null, or throw an exception, if you return null you should be carefull and handle it later.
And then use it to print the variable or null when not defined.
System.out.println(getLong(node, "id"));
EDIT (Acording to the edited question):
When you configure the object mapper you can specify how strict it should be with the configure method, you could use the DeserializationFeature enum to indicate when it should fail and when it shouldn't.
Here you can see each feature and what is for:
https://fasterxml.github.io/jackson-databind/javadoc/2.9/com/fasterxml/jackson/databind/DeserializationFeature.html
Now if the name of the properties in your class and json matches you can convert a json to a dto object just as follows:
ObjectMapper mapper = new ObjectMapper() // object mapper with wanted properties
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES,
mapper.readValue(json, MyDto.class);
Here you just need to create the mapper (no deserializer is needed) and then transform it to the destination class.
In case you have different names in you class and json you have to use the annotation #JsonProperty and specify the name.
Possible problems:
Now, for what I can see in your DTO your using final attributes, when you use them you have to create a constructor with arguments, and cannot create an empty contructor (at least without specified it's values beforehand), this empty constructor is necessary for a java POJO and is used internally by the object mapper.
So your DTO should be something like:
class MyDto implements Serializable {
private String firstName;
private String lastName;
private Integer age;
// empty constructor (only necessary when another constructor is specified)
// getters and setters
}
If you still need to use Immutable objects and not POJOs you can create a class like the following:
class MyDto implements Serializable {
private final String firstName;
private final String lastName;
private final Integer age;
#JsonCreator
MyDto(#JsonProperty("firstName") String firstName,
#JsonProperty("lastName") String lastName,
#JsonProperty("age") Integer age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
// getters
}
Using the mapper
With the POJO class and object mapper from above you can do the following:
MyDto myDto1 = mapper.readValue("{\"firstName\": \"name\"}", MyDto.class); // object with firstName defined, lastName and age are null
MyDto myDto2 = mapper.readValue("{\"firstName\": \"name\",\"lastName\": \"last\"}", MyDto.class); // object with firstName and lastName defined, age is null
MyDto myDto3 = mapper.readValue("{\"firstName\": \"name\",\"lastName\": \"last\",\"age\": 1}", MyDto.class); // object with firstName, lastName and age defined
And you can even use an empty object or have unknown properties.
MyDto myDto4 = mapper.readValue("{}", MyDto.class); // object with all properties null
MyDto myDto5 = mapper.readValue("{\"blablah\": \"name\"}", MyDto.class); // object with all properties null
I'm not clear how jackson deals with capitalization in mapping fields. If anyone could help I'd appreciate it.
{"user":{"username":"user#host.com","password":"pwd","sendercompid":"COMPID","service":{"host":"address","port":6666,"service":"S1","serviceAsString":"s1"}},"MDReqID":"ghost30022","NoRelatedSym":1,"Symbol":["GOOG"],"MarketDepth":"0","NoMDEntryTypes":3,"MDEntryType":["0","1","2"],"SubscriptionRequestType":"1","AggregatedBook":"N"}:
Above is my json, below is my exception...
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "MDReqID" (class com.myco.qa.fixrest.MarketDataRequest), not marked as ignorable (10 known properties: , "mdreqID", "marketDepth", "user", "subscriptionRequestType", "aggregatedBook", "mdentryType", "symbol", "mdupdateType", "noRelatedSym", "noMDEntryTypes"])
Above is my exception, below is my class...
public class MarketDataRequest {
private User user;
private String MDReqID;
private char SubscriptionRequestType;
private int MarketDepth;
private int MDUpdateType;
private char AggregatedBook;
private int NoMDEntryTypes;
private ArrayList<Character> MDEntryType;
private int NoRelatedSym;
private ArrayList<String> Symbol;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getMDReqID() {
return MDReqID;
}
public void setMDReqID(String MDReqID) {
this.MDReqID = MDReqID;
}
public char getSubscriptionRequestType() {
return SubscriptionRequestType;
}
public void setSubscriptionRequestType(char subscriptionRequestType) {
SubscriptionRequestType = subscriptionRequestType;
}
... et cetera
Since your setter method is named setMDReqID(…) Jackson assumes the variable is named mDReqID because of the Java naming conventions (variables should start with lower case letters).
If you really want a capital letter use the #JsonProperty annotation on the setter (or - for serialization - on the getter) like this:
#JsonProperty("MDReqID")
public void setMDReqID(String MDReqID) {
this.MDReqID = MDReqID;
}
You can also do
#JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
on the class to capitalise all property names in the JSON message
Add #JsonProperty on the setter that matches the property name in your received JSON string:
#JsonProperty("MDReqID")
public void setMDReqID(String MDReqID) {
this.MDReqID = MDReqID;
}
Additionally add #JsonProperty annotation to the getter as well for your output to appear in the conventional format:
#JsonProperty("mDReqID")
public String getMDReqID() {
return MDReqID;
}
Now you can name your variable whatever you like:
private String mdReqID;
I solve this problem by:
#Getter
#Setter
static class UserInfo {
//#JsonProperty("UUID")
private String UUID = "11";
private String UserName = "22";
private String userName = "33";
private String user_Name = "44";
private String user_name = "55";
private String User_name = "66";
private boolean HasDeleted=true;
private boolean hasDeleted=true;
private boolean has_Deleted=true;
private boolean has_deleted=true;
private boolean HAS_DELETED=true;
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
String s = objectMapper.writeValueAsString(new UserInfo());
System.out.println(s);
UserInfo userInfo = objectMapper.readValue(s, UserInfo.class);
System.out.println(objectMapper.writeValueAsString(userInfo));
}
output:
{"UUID":"11","UserName":"22","userName":"33","user_Name":"44","user_name":"55","User_name":"66","HasDeleted":true,"hasDeleted":true,"has_Deleted":true,"has_deleted":true,"HAS_DELETED":true}
I face the same problem , after have try UpperCamelCaseStrategy but still this error occurred , the strategy made my field pContent to ObjectMapper property Pcontent, as not want to add #JsonProperty for every field, simply use gson instead at last
Use JsonNaming Annotation to get all Class Field Names in Proper Case
Use lombok.Data Annotation to automatically make it work without adding getters and setters in your class
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import lombok.Data;
#JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class)
#Data