- Jackson JSON — Registering custom serializer and deserializer
- Example
- Java Object
- Custom JsonSerializer
- Custom JsonDeserializer
- Main class
- Using @JsonSerialize and @JsonDeserialize
- Example Project
- Jackson – Custom Serializer and Deserializer
- Jackson Custom Deserializer
- Technologies Used
- Gradle File
- 1. Create a Class extending StdDeserializer
- 2. Register Custom Deserializer with ObjectMapper
- 3. Register Custom Deserializer with @JsonDeserialize
Jackson JSON — Registering custom serializer and deserializer
Following example shows how to write custom serializer and deserializer and how to register them with ObjectMapper .
Example
Java Object
public class CurrencyRate
Custom JsonSerializer
package com.logicbig.example; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.format.FormatStyle; public class LocalDateTimeSerializer extends JsonSerializer < static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM); @Override public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException < try < String s = value.format(DATE_FORMATTER); gen.writeString(s); >catch (DateTimeParseException e) < System.err.println(e); gen.writeString(""); >> >
Custom JsonDeserializer
package com.logicbig.example; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import java.io.IOException; import java.time.LocalDateTime; import java.time.format.DateTimeParseException; public class LocalDatetimeDeserializer extends JsonDeserializer < @Override public LocalDateTime deserialize(JsonParser p, DeserializationContext ctx) throws IOException < String str = p.getText(); try < return LocalDateTime.parse(str, LocalDateTimeSerializer.DATE_FORMATTER); >catch (DateTimeParseException e) < System.err.println(e); return null; >> >
Main class
package com.logicbig.example; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import java.io.IOException; import java.time.LocalDateTime; public class ExampleMain < public static void main(String[] args) throws IOException < System.out.println("-- Java object to JSON --"); CurrencyRate cr = new CurrencyRate(); cr.setPair("USD/JPY"); cr.setRate(109.15); cr.setLastUpdated(LocalDateTime.now()); System.out.println("Java object: " + cr); ObjectMapper om = new ObjectMapper(); //creating a module SimpleModule module = new SimpleModule(); //adding our custom serializer and deserializer module.addSerializer(LocalDateTime.class ,new LocalDateTimeSerializer()); module.addDeserializer(LocalDateTime.class, new LocalDatetimeDeserializer()); //registering the module with ObjectMapper om.registerModule(module); String s2 = om.writeValueAsString(cr); System.out.println("JSON string: " + s2); System.out.println("-- JSON to Java object --"); CurrencyRate cr2 = om.readValue(s2, CurrencyRate.class); System.out.println("Java Object: " + cr2); >>
-- Java object to JSON --
Java object: CurrencyRate
JSON string:
-- JSON to Java object --
Java Object: CurrencyRate
Using @JsonSerialize and @JsonDeserialize
Instead of registering our custom Serializer and Deserializer with ObjectMapper, we can use @JsonSerialize and @JsonDeserialize , check out this example .
Example Project
Dependencies and Technologies Used:
- jackson-databind 2.9.5: General data-binding functionality for Jackson: works on core streaming API.
- JDK 10
- Maven 3.3.9
Jackson – Custom Serializer and Deserializer
Learn to create a custom serializer and custom deserializer for controlling the JSON to POJO conversion and vice versa using Jackson‘s StdSerializer and StdDeserializer classes.
Add the latest version of Jackson, if you have not already added it to the project.
com.fasterxml.jackson.core jackson-core $
For demo purposes, we will use the following Record and ArchiveStatus classes. We are using Lombok to reduce the boilerplate code such as getters, setters, constructors and toString() method.
@lombok.Data @AllArgsConstructor @NoArgsConstructor class Record < private Long id; private String message; private ZonedDateTime timestamp; private ArchiveStatus status; >@lombok.Data @AllArgsConstructor @NoArgsConstructor class ArchiveStatus
2. Default Serialization and The Requirement
By default, Jackson produces the JSON consisting of default fields structure and default value formats. For example, an instance of Record class will be serialized into the following format.
We want to customize the format to a custom format. The timestamp field should be used for display as received, and the status should be inline instead of a nested JSON field.
3. Creating Custom Serializer
Let us create a custom serializer according to our needs by extending StdSerializer class. The RecordSerializer‘s serialize() method will be invoked everytime Jackson needs to serialize its instance.
This class will create our desired JSON structure one field at a time. You can further customize the following code according to your needs. Note that DateTimeFormatter is a thread-safe object so we can reuse it with different threads.
class RecordSerializer extends StdSerializer < private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:ss:mm a z", Locale.US); protected RecordSerializer() < super(Record.class); >@Override public void serialize(Record value, JsonGenerator gen, SerializerProvider serializers) throws IOException < gen.writeStartObject(); gen.writeNumberField("id", value.getId()); gen.writeStringField("message", value.getMessage()); gen.writeStringField("timestamp", dtf.format(value.getTimestamp())); if (value.getStatus() != null) < gen.writeStringField("status", value.getStatus().getActive() ? "active" : "inactive"); >gen.writeEndObject(); > >
Now, if we serialize the Record instance then we will get the output JSON in the desired format.
@Test void testCustomSerialization() throws JsonProcessingException < ZonedDateTime zdt = ZonedDateTime.of(LocalDateTime.of(2022, 1, 1, 1, 1), ZoneId.of("GMT")); Record record = new Record(1L, "test-message", zdt, new ArchiveStatus(true)); JsonMapper jsonMapper = new JsonMapper(); String json = jsonMapper.writerWithDefaultPrettyPrinter() .writeValueAsString(record); Assertions.assertEquals("" , json); >
The custom deserializer is created by extending the StdDeserializer class. Its deserialize() method receives the parsed JSON as a JsonNode instance. We need to fetch the specific JSON fields from the JsonNode and build the Record instance.
class RecordDeserializer extends StdDeserializer < private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:ss:mm a z", Locale.US); public RecordDeserializer() < this(null); >public RecordDeserializer(Class vc) < super(vc); >@Override public Record deserialize(JsonParser parser, DeserializationContext ctx) throws IOException, JacksonException < JsonNode node = parser.getCodec().readTree(parser); Integer ((IntNode) node.get("id")).numberValue(); String message = node.get("message").asText(); String timestamp = node.get("timestamp").asText(); ArchiveStatus status = new ArchiveStatus(false); if(node.get("status") != null) < String active = node.get("status").asText(); if("active".equalsIgnoreCase(active)) < status.setActive(true); >> return new Record(id.longValue(), message, ZonedDateTime.parse(timestamp, dtf), status); > >
Now we can deserialize the custom JSON into the Record instance, as expected.
@Test void testCustomDeserialization() throws JsonProcessingException < ZonedDateTime zdt = ZonedDateTime .of(LocalDateTime.of(2022, 1, 1, 1, 1), ZoneId.of("GMT")); String json = ""; JsonMapper jsonMapper = new JsonMapper(); Record record = jsonMapper.readValue(json, Record.class); Assertions.assertEquals(1L, record.getId()); Assertions.assertEquals("test-message", record.getMessage()); Assertions.assertEquals(zdt, record.getTimestamp()); Assertions.assertEquals(true, record.getStatus().getActive()); >
4. Registering Custom Serializer and Deserializer
Let’s check out the different ways to register the above create serializer and deserializer classes with Jackson runtime.
The SimpleModule class registration of serializers and deserializers, bean serializer and deserializer modifiers, registration of subtypes and mix-ins, and some other commonly needed aspects.
JsonMapper jsonMapper = new JsonMapper(); SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(Record.class, new RecordSerializer()); simpleModule.addDeserializer(Record.class, new RecordDeserializer()); jsonMapper.registerModule(simpleModule);
4.2. Using @JsonSerialize and @JsonDeserialize
Another efficient way to register the custom handlers is using the @JsonSerialize and @JsonDeserialize annotations as follows:
@JsonSerialize(using = RecordSerializer.class) @JsonDeserialize(using = RecordDeserializer.class) class Record
When using annotations, we do not need to add the classes using SimpleModule registration process.
In this Jackson tutorial, we learned to create custom Jackson serializers and deserializers. We also learned to register the custom handlers using the SimpleModule as well as @JsonSerialize and @JsonDeserialize annotations.
Jackson Custom Deserializer
Jackson provides JsonDeserializer and its subclasses such as StdDeserializer to deserialize objects from JSON. According to Jackson, we should extend StdDeserializer or its subtypes like StdScalarDeserializer class to create custom deserializer instead of using JsonDeserializer abstract class. Here on this page we will provide custom deserializer example using StdDeserializer class. For custom deserialization, we need to do following below steps.
1. Create a class extending StdDeserializer and then override its deserialize() method.
2. Register the custom deserializer with ObjectMapper .
3. Or use @JsonDeserialize annotation.
Now find the complete example to create custom deserializer and use it step-by-step.
Contents
Technologies Used
Gradle File
apply plugin: ‘java’ apply plugin: ‘eclipse’ archivesBaseName = ‘concretepage’ version = ‘0.0.1-SNAPSHOT’ repositories < mavenCentral() >dependencies
1. Create a Class extending StdDeserializer
StdDeserializer is the base class to create common deserializers. To create a custom deserializer, we need to create a class extending StdDeserializer and then override its deserialize() method. We can use custom deserializer either by registering with ObjectMapper or annotating class with @JsonDeserialize .
Now find the JSON used in our demo.
package com.concretepage; import java.io.IOException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; public class MyCustomDeserializer extends StdDeserializer < private static final long serialVersionUID = 1L; public MyCustomDeserializer() < this(Company.class); >protected MyCustomDeserializer(Class vc) < super(vc); >@Override public Company deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException < TreeNode tn = p.readValueAsTree(); Integer compId; String compName; String city; String country; if (tn.get("compId") != null) < compId = Integer.parseInt(tn.get("compId").toString()); >else < compId = 000; >if (tn.get("compName") != null) < compName = tn.get("compName").toString() + "-Modified"; >else < compName = "NA"; >if (tn.get("compAddress") != null && tn.get("compAddress").get("city") != null) < city = tn.get("compAddress").get("city").toString() + "-Modified"; >else < city = "NA"; >if (tn.get("compAddress") != null && tn.get("compAddress").get("country") != null) < country = tn.get("compAddress").get("country").toString() + "-Modified"; >else < country = "NA"; >Address address = new Address(city, country); return new Company(compId, compName, address); > >
deserialize() method has following arguments.
JsonParser: Reads JSON content.
DeserializationContext: Context for the process of deserialization a single root-level value.
We can iterate JSON property as following. Here p is the instance of JsonParser in deserialize() method.
TreeNode tn = p.readValueAsTree(); Iterator compFields = tn.fieldNames(); while(compFields.hasNext()) < String field = compFields.next(); System.out.println(field); System.out.println(tn.get(field)); >System.out.println(«——————-«); Iterator addressFields = tn.get(«compAddress»).fieldNames(); while(addressFields.hasNext())
package com.concretepage; import com.fasterxml.jackson.annotation.JsonProperty; public class Address < @JsonProperty("city") private String city; @JsonProperty("country") private String country; public Address() < >public Address(String city, String country) < this.city = city; this.country = country; >public String getCity() < return city; >public String getCountry() < return country; >@Override public String toString() < return city + " | " + country; >>
package com.concretepage; import com.fasterxml.jackson.annotation.JsonProperty; public class Company < @JsonProperty("compId") private Integer id; @JsonProperty("compName") private String name; @JsonProperty("compAddress") private Address address; public Company() < >public Company(Integer id, String name, Address address) < this.id = id; this.name = name; this.address = address; >public Integer getId() < return id; >public String getName() < return name; >public Address getAddress() < return address; >@Override public String toString() < return id + " | " + name; >>
2. Register Custom Deserializer with ObjectMapper
To register custom deserializer with ObjectMapper , Jackson provides SimpleModule.addDeserializer() for specified type. Find the code snippet.
ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addDeserializer(Company.class, new MyCustomDeserializer()); mapper.registerModule(module);
package com.concretepage; import java.io.IOException; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; public class DeserializeDemo < public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException < String jsonData = "" +">"; ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addDeserializer(Company.class, new MyCustomDeserializer()); mapper.registerModule(module); Company company = mapper.readValue(jsonData, Company.class); System.out.println(company); System.out.println(company.getAddress()); > >
100 | "ABC Ltd"-Modified "Varanasi"-Modified | "India"-Modified
3. Register Custom Deserializer with @JsonDeserialize
We can register custom deserializer using @JsonDeserialize annotation by attaching to «setter» methods or fields, or to value classes. It has attributes such as using , as , keyAs , contentAs etc. using attribute specifies the custom deserializer class.
Here in our example, we will attach custom deserializer at class level. Find the example.
Company.java
package com.concretepage; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @JsonDeserialize(using= MyCustomDeserializer.class) public class Company < @JsonProperty("compId") private Integer id; @JsonProperty("compName") private String name; @JsonProperty("compAddress") private Address address; // >
ObjectMapper mapper = new ObjectMapper(); Company company = mapper.readValue(jsonData, Company.class); System.out.println(company); System.out.println(company.getAddress());