1. Introduction
In this example, we shall go through the most common Jackson API exceptions encountered while working for serialization and deserialization. We will see what caused the exception to be thrown and how to fix it. Let’s dive deep.
2. “InvalidDefinitionException: No Creators, like default constructor, exist”
2.1. Model
Consider the following model class Shop for deserialization operations for this example.
Shop.java
public class Shop { public int id; public String name; public Shop(int id, String name) { this.id = id; this.name = name; } }
2.2. Exception
The below code attempts to deserialize a JSON string to an object of class Shop. The code when executed results in an exception at runtime.
Deserialization
private static void jacksonNoConstructor() { ObjectMapper mapper = new ObjectMapper(); String json = "{"id":1,"name":"John"}"; try { mapper.readValue(json, Shop.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
An InvalidDefinitionException is thrown. The exception and the stack trace is:
Stack Trace
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `jackson.exceptions.Shop` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: .[Source: (String undefined)"{"id":1,"name":"John"}"; line: 1, column: 2] at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from( InvalidDefinitionException.java:67 undefined)
2.3. Problem
Jackson API during deserialization is unable to identify a suitable constructor to create an instance of the class Shop, which doesn’t define a no-argument default constructor.
2.4. Solution
Provide a default constructor in the Shop class as shown below to fix this error.Shop.java [with fix]
public class Shop { public int id; public String name; public Shop(int id, String name) { this.id = id; this.name = name; } public Shop() { } }
The deserialization after applying the fix will be successful without any exception at runtime.
3. MismatchedInputException: Out of START_ARRAY token
3.1. Model
Consider the following model class Customer with two fields id and name.Customer.java
public class Customer { public int id; public String name; }
3.2. Exception
The below code attempts to deserialize an array of JSON objects of type Customer to an instance of the Customer class. This code when executed throws an exception at runtime.Deserialization
private static void jacksonListToObject() { ObjectMapper mapper = new ObjectMapper(); String json = "[{"id":1,"name":"John"},{"id":2,"name":"Adam"}]"; try { mapper.readValue(json, Customer.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
A MismatchedInputException is thrown. The exception and the stack trace is:Stack Trace
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `jackson.exceptions.Customer` out of START_ARRAY token at [Source: (String)"[{"id":1,"name":"John"},{"id":2,"name":"Adam"}]"; line: 1, column: 1] at com.fasterxml.jackson.databind.exc. MismatchedInputException.from(MismatchedInputException.java:59)
3.3. Problem
We are trying to deserialize a JSON array to an object which is not of the type List but Customer. The above exception is thrown when we use the wrong type during deserialization.
3.4. Solution
The fix is to deserialize the JSON array to an object of type List<Customer> instead of type Customer. The below code demonstrates this.Deserialization [With Fix]
private static void jacksonListToObjectFix() { ObjectMapper mapper = new ObjectMapper(); String json = "[{"id":1,"name":"John"}," + "{"id":2,"name":"Adam"}]"; try { List<Customer> customer = mapper.readValue (json, new TypeReference<List>() { }); System.out.println(customer); // Prints [Customer [id=1, name=John], // Customer [id=2, name=Adam]] } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
The deserialization after applying the fix will convert the JSON array to a List<Customer> without throwing any exception at runtime.
4. InvalidDefinitionException: No properties discovered to create BeanSerializer
4.1. Model
Consider the following model class Product.Product.java
public class Product { int id; String name; public Product(int id, String name) { this.id = id; this.name = name; } }
4.2. Exception
The following code attempts to serialize an instance of the class Product. The code executes and errors out with an exception at runtime.Serialization
private static void privateFieldAndGetter() { ObjectMapper mapper = new ObjectMapper(); try { Product p = new Product(1, "Anmol"); mapper.writeValueAsString(p); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
An InvalidDefinitionException is thrown. The exception and the stack trace is:Stack Trace
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class jackson.exceptions.Product and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from (InvalidDefinitionException.java:77)
4.3. Problem
The Jackson API is unable to auto-detect public getters or public fields available in the model class for serialization. In other words, our model class does not expose any public getters or public fields.
4.4. Solution
There are multiple solutions to fix this problem. Let’s discuss each of them with examples.
4.4.1. Modify The Model Class
This is the easiest solution and we may use it only if we have an option to modify the model class and add public getters for all fields.[Fix] Add Public Getters
public int getId() { return id; } public String getName() { return name; }
4.4.2. ObjectMapper setVisibility method
We may also choose to configure the ObjectMapper by invoking the setVisibility method. Setting the visibility of FIELD to ANY will fix this problem as shown below.[Fix] ObjectMapper setVisibility
private static void privateFieldAndGetterFix() { ObjectMapper mapper = new ObjectMapper(); try { Product p = new Product(1, "Anmol"); mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); String json = mapper.writeValueAsString(p); System.out.println(json); // Prints {"id":1,"name":"Anmol"} } catch (JsonProcessingException e) { } }
4.4.3. @JsonAutoDetect annotation
We can also use the @JsonAutoDetect
annotation to fix this problem. For a detailed understanding of this annotation, check out the example on Jackson Annotations.[Fix] @JsonAutoDetect
@JsonAutoDetect(fieldVisibility = Visibility.ANY) public class Product {.....}
5. InvalidFormatException: Cannot deserialize value of type `int` from String
5.1. Model
Consider the following model class Customer.Customer.java
public class Customer { public int id; public String name; }
5.2. Exception
The below code attempts to deserialize a JSON string to an object of class Customer. The code when executed results in an exception at runtime.Deserialization
private static void cannotDeserialize() { ObjectMapper mapper = new ObjectMapper(); String json = "{"id":"Three","name":"Anmol"}"; try { mapper.readValue(json, Customer.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
5.3. Problem
Clearly we are trying to feed in value of an incompatible type String from the JSON to an integer field in our model class. This will not work as the text value “Three” is not a number(integer). Therefore, an InvalidFormatException is thrown.
5.4. Solution
In such cases, there are two extreme solutions.
One is to change this field’s type in the model class from int to String to make the JSON and model class compatible. The other solution is for the producer of the JSON string to serialize the value of the property in the correct type. i.e. int in this case.
6. UnrecognizedPropertyException: Unrecognized field { }
6.1. Model
Consider the following model class Customer for this example.Customer.java
public class Customer { public int id; public String name; }
6.2. Exception
The following code attempts to deserialize a JSON string and throws an exception.Deserialization
private static void unknownProperty() { ObjectMapper mapper = new ObjectMapper(); String json = "{"id":99,"name":"Anmol","location":"Bangalore"}"; try { mapper.readValue(json, Customer.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
6.3. Problem
This is a very common exception and is thrown when an unrecognized property is identified in the JSON string which is not available in the model class.
In the above code, the JSON string contains an additional field “location” unavailable in the class Customer causing the exception to be thrown.
6.4. Solution
This problem can be solved by using multiple approaches. Let’s discuss those.
6.4.1. The @JsonIgnore annotation
We can annotate our model class with @JsonIgnoreProperties
and define a rule to ignore unknown properties. The below example shows this.Customer.java [With Fix]
@JsonIgnoreProperties(ignoreUnknown = true) public class Customer { public int id; public String name; }
6.4.2. The DeserializationFeature
We may also use the DeserializationFeature FAIL_ON_UNKNOWN_PROPERTIES and disable it on the ObjectMapper. This will cause deserialization not to fail on detecting unknown properties.Deserialization [With Fix]
private static void unknownPropertyFix() { ObjectMapper mapper = new ObjectMapper(); String json = "{"id":99,"name":"Anmol","location":"Bangalore"}"; mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); try { mapper.readValue(json, Customer.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); e.printStackTrace(); } }
The deserialization after applying the fix will be successful without any exception at runtime.
7. MismatchedInputException: Root name { } does not match expected
7.1. Model
Consider the following model class Customer for this example.Customer.java
public class Customer { public int id; public String name; }
7.2. Exception
The below code attempts to deserialize a JSON string to an object of class Customer. The code when executed results in an exception at runtime.Deserialization
private static void wrongJsonRoot() { ObjectMapper mapper = new ObjectMapper(); String json = "{"jsonRoot" : {"id":1,"name":"John"}}"; try { mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE); mapper.readValue(json, Customer.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
A MismatchedInputException is thrown. The exception and the stack trace is:Stack Trace
com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name 'jsonRoot' does not match expected ('Customer') for type [simple type, class jackson.exceptions.Customer] at [Source: (String)"{"jsonRoot" : {"id":1,"name":"John"}}"; line: 1, column: 2] (through reference chain: jackson. exceptions.Customer["jsonRoot"]) at com.fasterxml.jackson.databind.exc.MismatchedInputException. from(MismatchedInputException.java:63)
7.3. Problem
In the above code, the UNWRAP_ROOT_VAUE deserialization feature has been enabled on the ObjectMapper. This feature will unwrap the root-level JSON value while deserialization.
However, the Jackson API is unable to locate in the model class, the root name provided in the JSON string. This causes a MismatchedInputException to be thrown.
7.4. Solution
To fix this problem, we need to annotate our model class with the annotation @JsonRootName
and define the name to be used for root-level wrapping. This is shown in the example below.Customer.java [With Fix]
@JsonRootName("jsonRoot") public class Customer { public int id; public String name; }
Deserialization After Fix
private static void wrongJsonRootFix() { ObjectMapper mapper = new ObjectMapper(); String json = "{"jsonRoot" : {"id":1,"name":"John"}}"; try { mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE); Customer customer = mapper.readValue(json, Customer.class); System.out.println(customer.name + " - " + customer.id); // Print John - 1 } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
The above deserialization code will now execute without throwing any exception.
8. JsonParseException: Unexpected character (code 39)
8.1. Model
Consider the following model class Employee for this example.Employee.java
public class Employee { public int id; public String name; }
8.2. Exception
The following code attempts to deserialize a JSON string to an instance of class Employee and errors out with an exception.Deserialization
private static void code39Exception() { ObjectMapper mapper = new ObjectMapper(); String json = "{'id':99,'name':'Anmol'}"; try { mapper.readValue(json, Employee.class); } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
A JsonParseException is thrown. The exception and stack trace is:Stack Trace
com.fasterxml.jackson.core.JsonParseException: Unexpected character (''' (code 39)): was expecting double-quote to start field name at [Source: (String)"{'id':99,'name':'Anmol'}"; line: 1, column: 3] at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1851)
8.3. Problem
If we carefully observe the deserialization code in the section above, the JSON string is composed of single-quotes instead of double-quotes. Therefore, deserializing a JSON string with single-quotes results in a code 39 error.
8.4. Solution
The ObjectMapper class provides a constructor to which we can supply our own JsonFactory with some features enabled. The fix for this problem is also around the same lines. We can configure the ObjectMapper to allow single quotes as demonstrated in the below example.[Fix] A JsonFactory With Single Quotes enabled, supplied to ObjectMapper
private static void code39Fix() { String json = "{'id':99,'name':'Anmol'}"; try { JsonFactory jf = new JsonFactory(); jf.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES); ObjectMapper mapper = new ObjectMapper(jf); Employee employee = mapper.readValue(json, Employee.class); System.out.println(employee.id + " = " + employee.name); // Prints 99 = Anmol } catch (JsonProcessingException e) { System.out.println(e.getClass().getName() + " : " + e.getOriginalMessage()); } }
9. Summary
In this article, we spoke about Jackson Exceptions. Specifically, we explored some common exceptions that are encountered in programming with the Jackson API for serialization/deserialization. We did a deep dive into the causes of such exceptions and looked at the way to fix them.
10. Download the source code
Hello,
I am trying to get the following JSON response into a list so I can reference on value and get another :
{ "Result": [ { "Key": "19e848a2-cbb6-4c72-8e24-05ce47da7754", "Name": "Bistro" }, { "Key": "d274591a-06fd-4d50-a973-5a629a3a6d3a", "Name": "Sports Bar" }, { "Key": "b648abd8-da53-4089-a7f9-5ef7ddece38c", "Name": "Constable Suite" }, { "Key": "69a4b8b9-427d-4cca-b62a-62e6cbc8a27c", "Name": "Haywain Suite" }, { "Key": "142a6bc3-86ac-4953-a022-8641f318ffa0", "Name": "Hotel Lounge Main Bar" }, { "Key": "6294ae4e-273b-408a-8e5f-df5853badf90", "Name": "Garden Room" }, { "Key": "f0516ab6-9bce-4f35-aa40-f8f49c464023", "Name": "Gallery Grill" } ], "RID": "1", "CloudDateTime": "2020-07-04T08:16:36.0000000Z", "Success": true, "Message": "" }
and I have defined my classes as
public class Dept { public Result _Result { get; set; } public string _RID { get; set; } public DateTime _CloudDateTime { get; set; } public bool _Success { get; set; } public string _Message { get; set; } } public class Result { public string _Key { get; set; } public string _Name { get; set; } }
but, when i use the following to grab the data :
List<Dept> Departments = JsonConvert.DeserializeObject<List<Dept>>(response.Content); foreach (var item in Departments) { var _Key = item._Result._Key; var _Name = item._Result._Name; richTextBox5.AppendText(_Key + " - " + _Name + Environment.NewLine); }
I get the message
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[myprogram.Form1+Dept]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object. Path 'Result', line 1, position 10.
if I change public Results _Result {get; set;} to public List<Result> _Result { get; set; }
I then get swiggles un der _Key and _Name
var _Key = item._Result._Key; var _Name = item._Result._Name;
saying «does not contain a definition for «_Key» ….»
How do I get eh response into a list so I can then reference the name to the key ie :
if(_Name == "Bistro") { var deptKey = theCorrespondingDeptKeyFromList MessageBox.show(_Name + " " + deptKey) }
thanks
Photo by Annie Spratt
Spring Boot projects primarily use the JSON library Jackson to serialize and deserialize objects. It is especially useful that Jackson automatically serializes objects returned from REST APIs and deserializes complex type parameters like @RequestBody
.
In a Spring Boot project the automatically registered MappingJackson2HttpMessageConverter
is usually enough and makes JSON conversions simple, but this may have some issues which need custom configuration. Let’s go over a few good practices for them.
Configuring a Custom Jackson ObjectMapper
In Spring REST projects a custom implementation of MappingJackson2HttpMessageConverter
helps to create the custom ObjectMapper
, as seen below. Whatever custom implementation you need to add to the custom ObjectMapper
can be handled by this custom converter:
public class CustomHttpMessageConverter extends MappingJackson2HttpMessageConverter {
private ObjectMapper initCustomObjectMapper() {
ObjectMapper customObjectMapper = new ObjectMapper();
return customObjectMapper;
}
// ...
}
Additionally, some MappingJackson2HttpMessageConverter
methods, such as writeInternal
, can be useful to override in certain cases. I’ll give a few examples in this article.
In Spring Boot you also need to register a custom MappingJackson2HttpMessageConverter
like below:
@Bean
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
return new CustomHttpMessageConverter();
}
Serialization
Pretty-printing
Pretty-printing in Jackson is disabled by default. By enabling SerializationFeature.INDENT_OUTPUT
in the ObjectMapper
configuration pretty-print output is enabled (as in the example below). Normally a custom ObjectMapper
is not necessary for setting the pretty-print configuration. In some cases, however, like one case of mine in a recent customer project, this configuration might be necessary.
For example, passing a URL parameter can enable pretty-printing. In this case having a custom ObjectMapper
with pretty-print enabled and keeping the default ObjectMapper
of MappingJackson2HttpMessageConverter
as is could be a better option.
public class CustomHttpMessageConverter extends MappingJackson2HttpMessageConverter {
private ObjectMapper initiatePrettyObjectMapper() {
ObjectMapper customObjectMapper = new ObjectMapper();
customObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
// additional indentation for arrays
DefaultPrettyPrinter pp = new DefaultPrettyPrinter();
pp.indentArraysWith(new DefaultIndenter());
customObjectMapper.setDefaultPrettyPrinter(pp);
return customObjectMapper;
}
}
Conditionally Filtering the Fields
When serializing a response object you may need to include or ignore one or more fields depending on their values. Let’s assume a model class UserResponse
like below.
Notice that we used @JsonIgnore
which is completely discarding the annotated field from serialization. Conditional filtering is different and it can be done using SimpleBeanPropertyFilter
objects set to the filter provider of the ObjectMapper
objects. Also notice that @JsonFilter
annotation is used for UserResponse
which points to which filter will be used by ObjectMapper
during the serialization.
@JsonFilter("userCodeFilter")
public class UserResponse {
public Integer userId;
public String username;
public Integer code;
@JsonIgnore
public String status;
}
Here we add a filter called userCodeFilter
—like the one we added to the custom ObjectMapper
of CustomHttpMessageConverter
—which will include the UserResponse
class’s code field in the serialization if its value is greater than 0. You can add multiple filters to ObjectMapper
for different models.
public class CustomHttpMessageConverter extends MappingJackson2HttpMessageConverter {
private ObjectMapper initiatePrettyObjectMapper() {
ObjectMapper customObjectMapper = new ObjectMapper();
customObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
// additional indentation for arrays
DefaultPrettyPrinter pp = new DefaultPrettyPrinter();
pp.indentArraysWith(new DefaultIndenter());
customObjectMapper.setDefaultPrettyPrinter(pp);
PropertyFilter userCodeFilter = new SimpleBeanPropertyFilter() {
@Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
throws Exception {
if (include(writer)) {
if (!writer.getName().equals("code")) {
writer.serializeAsField(pojo, jgen, provider);
return;
}
int intValue = ((UserResponse) pojo).code;
if (intValue > 0) {
writer.serializeAsField(pojo, jgen, provider);
}
} else if (!jgen.canOmitFields()) {
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
@Override
protected boolean include(BeanPropertyWriter writer) {
return true;
}
@Override
protected boolean include(PropertyWriter writer) {
return true;
}
};
FilterProvider filters = new SimpleFilterProvider().addFilter("userCodeFilter", userCodeFilter);
customObjectMapper.setFilterProvider(filters);
return customObjectMapper;
}
}
Deserialization
JSON String Parse Error Handling in Spring Boot
This one is a little tricky. Deserialization of a JSON @RequestParam
object can cause parsing errors if the JSON object is not well-formed. The errors thrown in Jackson’s deserialization level just before it’s pushed to Spring Boot occur at that level, so Spring Boot doesn’t catch these errors.
Deserialization of Jackson maps JSON to POJOs and finally returns the expected Java class object. If the JSON is not well-formed, parsing cannot be done and MappingJackson2HttpMessageConverter
internally throws a parsing error. Since this exception is not caught by Spring Boot and no object is returned, the REST controller would be unresponsive, having a badly-formed JSON payload.
Here we can override the internal read
method of MappingJackson2HttpMessageConverter
, hack the ReadJavaType
with a customReadJavaType
method, and make it return an internal error when the deserialization fails to parse the JSON input, rather than throwing an exception which is not seen or handled by Spring Boot.
@Override
public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
objectMapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
JavaType javaType = getJavaType(type, contextClass);
return customReadJavaType(javaType, inputMessage);
}
private Object customReadJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
try {
if (inputMessage instanceof MappingJacksonInputMessage) {
Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
if (deserializationView != null) {
return this.objectMapper.readerWithView(deserializationView).forType(javaType).
readValue(inputMessage.getBody());
}
}
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
}
catch (InvalidDefinitionException ex) {
//throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
return "Type definition error";
}
catch (JsonProcessingException ex) {
//throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
return "JSON parse error";
}
}
This way you can return errors occurring at the deserialization level to Spring Boot, which expects a deserialized object but gets a String
value which can be caught and translated into a ControllerAdvice
handled exception. This also makes it easier to catch JSON parsing errors without using any third party JSON libraries like Gson.
json
rest
java
frameworks
spring