JSON (JavaScript Object Notation) is the common way of communication medium from multiple API responses that helps to get the data. Data will be helpful to render in multiple web pages, android or ios apps. JSON comes with a standard and any text that contains a JSON object or JSON array has to satisfy the standard. In a maven java project, the below dependencies need to be used for availing Jackson.
XML
< dependencies > < dependency > < groupId >com.fasterxml.jackson.core</ groupId > < artifactId >jackson-core</ artifactId > < version >2.5.0</ version > </ dependency > < dependency > < groupId >com.fasterxml.jackson.core</ groupId > < artifactId >jackson-databind</ artifactId > < version >2.5.0</ version > </ dependency > < dependency > < groupId >com.fasterxml.jackson.core</ groupId > < artifactId >jackson-annotations</ artifactId > < version >2.5.0</ version > </ dependency > </ dependencies > |
While handling the JSON data using Jackson, exceptions also need to be handled gracefully. In this post, let us see how to handle it by seeing a sample java project. As it is a maven project, let us check with pom.xml. Above said dependencies are present in pom.xml.
Example Project
pom.xml
XML
<? xml version = "1.0" encoding = "UTF-8" ?> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 < modelVersion >4.0.0</ modelVersion > < artifactId >jackson-exceptions</ artifactId > < version >0.0.1-SNAPSHOT</ version > < name >jackson-exceptions</ name > < groupId >com.gfg</ groupId > < dependencies > < dependency > < groupId >com.fasterxml.jackson.core</ groupId > < artifactId >jackson-core</ artifactId > < version >2.5.0</ version > </ dependency > < dependency > < groupId >com.fasterxml.jackson.core</ groupId > < artifactId >jackson-databind</ artifactId > < version >2.5.0</ version > </ dependency > < dependency > < groupId >com.fasterxml.jackson.core</ groupId > < artifactId >jackson-annotations</ artifactId > < version >2.5.0</ version > </ dependency > < dependency > < groupId >junit</ groupId > < artifactId >junit</ artifactId > < version >4.12</ version > </ dependency > </ dependencies > < build > < finalName >jackson-exceptions</ finalName > < resources > < resource > < directory >src/main/resources</ directory > < filtering >true</ filtering > </ resource > </ resources > </ build > </ project > |
Let us see the src folder files first. They are mainly model files
AnimalLives.java
Java
public class AnimalLives { public PetAnimal animal; public AnimalLives() { } } abstract class PetAnimal { public String name; public PetAnimal() { } } class Cat extends PetAnimal { public int lives; public Cat() { } } |
AnimalLivesConfigured.java
Java
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; public class AnimalLivesConfigured { public PetAnimalConfigured animal; public AnimalLivesConfigured() { } } @JsonDeserialize (as = CatConfigured. class ) abstract class PetAnimalConfigured { public String name; public PetAnimalConfigured() { } } class CatConfigured extends PetAnimalConfigured { public int lives; public CatConfigured() { } } |
In this file, as we mentioned @JsonDeserialize, there is no need to register it on the ObjectMapper.
Author.java
Java
public class Author { public int id; public String name; public Author() { super (); } public Author( final int id, final String name) { this .id = id; this .name = name; } // API public int getId() { return id; } public String getName() { return name; } } |
AuthorWithConflict.java
Java
public class AuthorWithConflict { public int id; public String name; boolean checked; public AuthorWithConflict() { super (); } public AuthorWithConflict( final int id, final String name, final boolean checked) { this .id = id; this .name = name; this .checked = checked; } public boolean getChecked() { return checked; } public boolean isChecked() { return checked; } } |
AuthorWithNoDefaultConstructor.java
Java
public class AuthorWithNoDefaultConstructor { private int id; private String name; public AuthorWithNoDefaultConstructor( final int id, final String name) { this .id = id; this .name = name; } public int getId() { return id; } public String getName() { return name; } } |
AuthorWithPrivateFields.java
Java
public class AuthorWithPrivateFields { int id; String name; public AuthorWithPrivateFields() { super (); } public AuthorWithPrivateFields( final int id, final String name) { this .id = id; this .name = name; } } |
AuthorWithRoot.java
Java
import com.fasterxml.jackson.annotation.JsonRootName; @JsonRootName (value = "user" ) public class AuthorWithRoot { public int id; public String name; public AuthorWithRoot() { super (); } public AuthorWithRoot( final int id, final String name) { this .id = id; this .name = name; } } |
@JsonRootName annotation is used to indicate the name of the POJO, here AuthorWithRoot and it should be serialized. For serialization to work with @JsonRootName, enabling of SerializationFeature.WRAP_ROOT_VALUE is needed. For deserialization, enabling of DeserializationFeature.UNWRAP_ROOT_VALUE, is needed
ExampleForNoAccessors.java
Java
public class ExampleForNoAccessors { String stringValue; int intValue; boolean booleanValue; public ExampleForNoAccessors() { super (); } public ExampleForNoAccessors( final String stringValue, final int intValue, final boolean booleanValue) { super (); this .stringValue = stringValue; this .intValue = intValue; this .booleanValue = booleanValue; } } |
ExampleForNoAccessorsAndFieldVisibility.java
Java
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; @JsonAutoDetect (fieldVisibility = Visibility.ANY) public class ExampleForNoAccessorsAndFieldVisibility { String stringValue; int intValue; boolean booleanValue; public ExampleForNoAccessorsAndFieldVisibility() { super (); } public ExampleForNoAccessorsAndFieldVisibility( final String stringValue, final int intValue, final boolean booleanValue) { super (); this .stringValue = stringValue; this .intValue = intValue; this .booleanValue = booleanValue; } } |
By default, Jackson can able to access public fields while doing serialization and deserialization. public getters/setters are the additional ways in case public fields are not present. By using @JsonAutoDetect annotation and its visibility role of setting to ANY, even the private fields can be accessible. If this is not given, we will end up with an exception saying as the model class does not have getter/setter methods. Let us add the test classes now to test it.
JacksonExceptionsUnitTestExample.java
Java
import static org.junit.matchers.JUnitMatchers.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import java.io.IOException; import java.util.List; import org.junit.Test; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; import com.gfg.exceptions.Author; import com.gfg.exceptions.AuthorWithNoDefaultConstructor; import com.gfg.exceptions.AuthorWithPrivateFields; import com.gfg.exceptions.AuthorWithRoot; import com.gfg.exceptions.AnimalLives; import com.gfg.exceptions.AnimalLivesConfigured; public class JacksonExceptionsUnitTestExample { // JsonMappingException: Can not construct instance of @Test (expected = JsonMappingException. class ) public void givenAbstractClass_whenDeserializing_thenException() throws IOException { final String json = "{\"animal\":{\"name\":\"rocky\"}}" ; final ObjectMapper mapper = new ObjectMapper(); mapper.reader() .forType(AnimalLives. class ) .readValue(json); } @Test public void givenAbstractClassConfigured_whenDeserializing_thenCorrect() throws IOException { final String json = "{\"animal\":{\"name\":\"bruce\"}}" ; final ObjectMapper mapper = new ObjectMapper(); mapper.reader() .forType(AnimalLivesConfigured. class ) .readValue(json); } // JsonMappingException: No serializer found for class @Test (expected = JsonMappingException. class ) public void givenClassWithPrivateFields_whenSerializing_thenException() throws IOException { final AuthorWithPrivateFields user = new AuthorWithPrivateFields( 1 , "Rachel" ); final ObjectMapper mapper = new ObjectMapper(); mapper.writer() .writeValueAsString(user); } @Test public void givenClassWithPrivateFields_whenConfigureSerializing_thenCorrect() throws IOException { final AuthorWithPrivateFields user = new AuthorWithPrivateFields( 1 , "Ross" ); final ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); final String result = mapper.writer() .writeValueAsString(user); assertThat(result, containsString( "Ross" )); } // JsonMappingException: No suitable constructor found @Test (expected = JsonMappingException. class ) public void givenNoDefaultConstructor_whenDeserializing_thenException() throws IOException { final String json = "{\"id\":1,\"name\":\"Chandler\"}" ; final ObjectMapper mapper = new ObjectMapper(); mapper.reader() .forType(AuthorWithNoDefaultConstructor. class ) .readValue(json); } @Test public void givenDefaultConstructor_whenDeserializing_thenCorrect() throws IOException { final String json = "{\"id\":1,\"name\":\"Joey\"}" ; final ObjectMapper mapper = new ObjectMapper(); final Author user = mapper.reader() .forType(Author. class ) .readValue(json); assertEquals( "Joey" , user.name); } // JsonMappingException: Root name does not match expected @Test (expected = JsonMappingException. class ) public void givenWrappedJsonString_whenDeserializing_thenException() throws IOException { final String json = "{\"user\":{\"id\":1,\"name\":\"Monica\"}}" ; final ObjectMapper mapper = new ObjectMapper(); mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE); mapper.reader() .forType(Author. class ) .readValue(json); } @Test public void givenWrappedJsonStringAndConfigureClass_whenDeserializing_thenCorrect() throws IOException { final String json = "{\"user\":{\"id\":1,\"name\":\"Phoebe\"}}" ; final ObjectMapper mapper = new ObjectMapper(); mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE); final AuthorWithRoot user = mapper.reader() .forType(AuthorWithRoot. class ) .readValue(json); assertEquals( "Phoebe" , user.name); } } |
While checking with the methods available in the test classes, by using deserialization, exceptions are handled. Hence on running the above tests, we can see everything got passed
Let’s do one more test.
JacksonMappingExceptionUnitTestExample.java
Here field visibility testing is carried out. In ‘ExampleForNoAccessors’, we have private fields and hence they cannot be accessed without getter and setter methods. But in ‘ExampleForNoAccessorsAndFieldVisibility’, though we have private fields, they can be accessed easily without getter and setter but we have ‘@JsonAutoDetect’ annotation and its fieldVisibility is set to Visibility.ANY
Java
import static org.junit.Assert.assertThat; import static org.junit.matchers.JUnitMatchers.containsString; import java.io.IOException; import org.junit.Test; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.ObjectMapper; public class JacksonMappingExceptionUnitTestExample { @Test public final void givenObjectHasNoAccessors_whenSerializingWithPrivateFieldsVisibility_thenNoException() throws JsonParseException, IOException { final ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); final String dtoAsString = objectMapper.writeValueAsString( new ExampleForNoAccessors()); assertThat(dtoAsString, containsString( "intValue" )); assertThat(dtoAsString, containsString( "stringValue" )); assertThat(dtoAsString, containsString( "booleanValue" )); } @Test public final void givenObjectHasNoAccessorsButHasVisibleFields_whenSerializing_thenNoException() throws JsonParseException, IOException { final ObjectMapper objectMapper = new ObjectMapper(); final String dtoAsString = objectMapper.writeValueAsString( new ExampleForNoAccessorsAndFieldVisibility()); assertThat(dtoAsString, containsString( "intValue" )); assertThat(dtoAsString, containsString( "stringValue" )); assertThat(dtoAsString, containsString( "booleanValue" )); } } |
On running the above JUNIT code, we get the following results
Conclusion
Throughout a software project, we will come across multiple JSON outputs. While handling JSON, if they are not deserialized properly, Jackson exceptions will arise. We have to commonly use certain annotations like
- @JsonDeserialize(as = <Required class name>)
- @JsonRootName(value = “<class name>”)
- @JsonAutoDetect(fieldVisibility = Visibility.ANY)
By using the annotations in the required places, we can come over the Jackson exceptions.