Custom Field Name with @JsonProperty
1. Introduction
Custom field name with @JsonProperty
is very useful when mapping Java fields to JavaScript Object Notation (JSON) properties and vice versa. JSON is commonly used in web applications to transmit data and the clients and servers can be written in different programming languages with different naming conventions. For example, JSON adapts the “snake_case” for the property name while Java uses the “camelCase” format for the field name. The @JsonProperty annotation from the Jackson library maps Java fields to JSON properties that meet both naming conventions. In this example, I will demonstrate custom field name with @JsonProperty
annotation usage from the Jackson library.
2. Setup
In this step, I will create a Maven project which depends on Jackson
, Junit
, and Lombok
. The Lombok is included to reduce the boilerplate code.
pom.xml
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > < modelVersion >4.0.0</ modelVersion > < groupId >org.zheng.demo</ groupId > < artifactId >jackson-jsonproperty</ artifactId > < version >0.0.1-SNAPSHOT</ version > < dependencies > <!-- < dependency > < groupId >com.fasterxml.jackson.core</ groupId > < artifactId >jackson-databind</ artifactId > < version >2.17.1</ version > </ dependency > <!-- < dependency > < groupId >com.fasterxml.jackson.core</ groupId > < artifactId >jackson-annotations</ artifactId > < version >2.17.1</ version > </ dependency > < dependency > < groupId >org.junit.jupiter</ groupId > < artifactId >junit-jupiter-api</ artifactId > < version >5.10.2</ version > < scope >test</ scope > </ dependency > < dependency > < groupId >org.projectlombok</ groupId > < artifactId >lombok</ artifactId > < version >1.18.32</ version > < scope >provided</ scope > </ dependency > </ dependencies > </ project > |
3. Custom Field Name with @JsonProperty
In this step, I will create three POJO classes which annotate a custom field name with jsonproperty
annotation at the field
, getter
, and constructor
positions.
Customer.java:
@JsonProperty
is placed at thedataType
field.Order.java
:@JsonProperty
is placed at thegetName()
method.Person.java
@JsonProperty
is placed at thePerson
constructor.
3.1. Custom Field Name with @JsonProperty at Field
In this step, I will create a Customer.java
class which annotates the @JsonProperty
in the dataType
field and maps the dataType
field to JSON type
property. Note: cannot name the field name as type
directly as it is a reserved keyword in Java.
Customer.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | package org.zheng.demo.data; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.RequiredArgsConstructor; @Data @NoArgsConstructor @RequiredArgsConstructor @JsonIgnoreProperties (ignoreUnknown = true ) public class Customer implements Serializable { private static final long serialVersionUID = -1319171342148919329L; @JsonProperty ( "type" ) private String dataType; @NonNull private String email; private int id; private List<Order> orders; private Person person; public void addOrder(Order order) { if ( this .orders == null ) { this .orders = new ArrayList<>(); } this .orders.add(order); } } |
- Line 22: maps the Java
dataType
field to the JSONtype
property. - Line 25: marks the
email
field as@NonNull
value.
3.2 Custom Field Name with @JsonProperty at Getter
In this step, I will create an Order.java
class which annotates the @JsonProperty
at the getProductName
method. The JSON property “product_name
” maps to the Java “productName
” field.
Order.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | package org.zheng.demo.data; import java.util.Objects; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @JsonIgnoreProperties (ignoreUnknown = true ) public class Order { private String productName; private int quantity; public Order() { super (); } public Order(String productName, int quantity) { super (); this .productName = productName; this .quantity = quantity; } @Override public boolean equals(Object obj) { if ( this == obj) return true ; if (obj == null ) return false ; if (getClass() != obj.getClass()) return false ; Order other = (Order) obj; return Objects.equals(productName, other.productName) && quantity == other.quantity; } @JsonProperty ( "product_name" ) public String getProductName() { return productName; } public int getQuantity() { return quantity; } @Override public int hashCode() { return Objects.hash(productName, quantity); } public void setProductName(String productName) { this .productName = productName; } public void setQuantity( int quantity) { this .quantity = quantity; } } |
- Line 37: maps the Java “
productName
” field to “product_name
” JSON property.
3.3 Custom Field Name with @JsonProperty at Constructor
In this step, I will create a Person.java
class that annotates the @JsonProperty
at the Person
constructor. It also marks the product_name
as required.
Person.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | package org.zheng.demo.data; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor public class Person { private int age; @JsonProperty ( "person_name" ) private String name; @JsonCreator public Person( @JsonProperty (value = "person_name" , required = true ) String name, @JsonProperty ( "age" ) int age) { this .name = name; this .age = age; } } |
- Line 13: annotates the field to “
person_name
“. - Line 16: marks the constructor used for the deserialization process.
- Line 17: maps the JSON
person_name
property to Java name field and marks it as a required field.
4. Test Person
In this step, I will create a Junit TestPerson.java
which has the following three tests:
test_happy_path
– it serialized and deserialized data as expected.test_required_throw_exception_if_not_there
– when the requiredperson_name
property is missing, then throwsMismatchedInputException
.test_required_with_null_is_ok
– when the requiredperson_name
property has anull
value, then it maps tonull
. Note: therequired=true
is different from@NonNull
annotation.
TestPerson.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | package org.zheng.demo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; import org.zheng.demo.data.Person; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.MismatchedInputException; class TestPerson { private ObjectMapper ob = new ObjectMapper(); @Test void test_happy_path() { // name and age have value String json = "{\"person_name\":\"John Doe\",\"age\":30}" ; try { Person pers = ob.readValue(json, Person. class ); assertEquals( "John Doe" , pers.getName()); assertEquals( 30 , pers.getAge()); } catch (JsonProcessingException e) { e.printStackTrace(); } } @Test void test_required_throw_exception_if_not_there() { // name is not there, String jsonWOName = "{\"name1\":\"John Doe\",\"age\":30}" ; MismatchedInputException expectedException = assertThrows(MismatchedInputException. class , () -> ob.readValue(jsonWOName, Person. class )); assertTrue(expectedException.getMessage().contains( "Missing required creator property 'person_name'" ) ); } @Test void test_required_with_null_is_ok() { // has name but the value is null String jsonNullName = "{\"person_name\":null,\"age\":30}" ; try { Person pers2 = ob.readValue(jsonNullName, Person. class ); assertNull(pers2.getName()); assertEquals( 30 , pers2.getAge()); } catch (JsonProcessingException e) { e.printStackTrace(); } } } |
- Line 22: the
person_name
maps to a non-null value. - line 36: throw an exception if JSON does not have required
person_name.
- line 45: the
person_name
maps anull
value.
5. Test Order
5.1 Order.json
In this step, I will create an Order.json
file and will be used in step 5.2.
Order.json
1 2 3 | { "quantity" : 10, "product_name" : "test" } |
- the
product_name
is mapped toproductName
.
5.2 TestOrder.java
In this step, I will create a Junit TestOrder.java
which has the following two tests:
test_ignore_unknown
– the unknown JSON properties are ignored.test_JsonProperty_getter_happypath
– JSON properties are mapped to POJO fields as expected with the@JsonProperty
set at thegetName
method.
TestOrder.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | package org.zheng.demo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import java.io.File; import java.io.IOException; import org.junit.jupiter.api.Test; import org.zheng.demo.data.Order; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; class TestOrder { private ObjectMapper ob = new ObjectMapper(); @Test void test_ignore_unknown() { // name and age have value String json = "{\"productName\":\"PC\",\"quantity\":3}" ; try { Order order = ob.readValue(json, Order. class ); assertNull(order.getProductName()); assertEquals( 3 , order.getQuantity()); } catch (JsonProcessingException e) { e.printStackTrace(); } } @Test void test_JsonProperty_getter_happypath() { File file = new File( "src/test/resources/order.json" ); try { Order order = ob.readValue(file, Order. class ); assertEquals( "test" , order.getProductName()); assertEquals( 10 , order.getQuantity()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } |
- Line 22: the JSON has “
productName
” which is unknown since@JsonProperty
maps to “product_name
“. - Line 25: the deserialized
order
object has anull
value in theproductName
field.
6. Test Customer
In this step, I will create a Junit TestCustomer.java
which has the following seven tests:
test_customer_full_happyPath
– read thecustomer_full.json
and map to Java POJO as expected.test_customer_no_email_throw_JsonMappingException
– throwJsonMappingException
when@NonNull
email is not there.test_customer_no_person_is_ok
– although theperson
class requires aperson_name
, but thecustomer
class does not require aperson
, so acustomer
‘sperson
is mapped tonull
.test_customer_null_email_throw_JsonMappingException
– because theemail
field is defined as non-null, so an exception is thrown when theob.readValue()
method is called.test_customer_person_no_name_throw_MismatchedInputException
– When a customer JSON contains aperson
to thecustomer
, butperson_name
is missing, then an exception is thrown as theperson_name
is required.test_customer_wo_email_personName_throws_MismatchedInputException
– when non-null email is missing and requiredperson_name
is missing, then missingperson_name
exception is thrown.test_JsonProperty_field
– verify that the@email
field annotated at field level works as expected.
TestCustomer.java
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | package org.zheng.demo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.io.IOException; import org.junit.jupiter.api.Test; import org.zheng.demo.data.Customer; import org.zheng.demo.data.Order; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.MismatchedInputException; class TestCustomer { private ObjectMapper ob = new ObjectMapper(); @Test void test_customer_full_happyPath() { File file = new File( "src/test/resources/customer_full.json" ); try { Customer readCust = ob.readValue(file, Customer. class ); assertEquals( "Mary" , readCust.getPerson().getName()); assertEquals( 50 , readCust.getPerson().getAge()); assertEquals( "test@test.com" , readCust.getEmail()); assertEquals( "major" , readCust.getDataType()); assertEquals( 2 , readCust.getId()); assertEquals( 2 , readCust.getOrders().size()); } catch (JsonProcessingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } @Test void test_customer_no_email_throw_JsonMappingException() { File file = new File( "src/test/resources/customer_no_email.json" ); JsonMappingException expectedException = assertThrows(JsonMappingException. class , () -> { ob.readValue(file, Customer. class ); }); assertTrue(expectedException.getMessage().contains( "email is marked non-null but is null" )); } @Test void test_customer_no_person_is_ok() { File file = new File( "src/test/resources/customer_no_person.json" ); try { Customer cust = ob.readValue(file, Customer. class ); assertNull(cust.getPerson()); } catch (IOException e) { e.printStackTrace(); } } @Test void test_customer_null_email_throw_JsonMappingException() { File file = new File( "src/test/resources/customer_null_email.json" ); JsonMappingException expectedException = assertThrows(JsonMappingException. class , () -> { ob.readValue(file, Customer. class ); }); assertTrue(expectedException.getMessage().contains( "email is marked non-null but is null" )); } @Test void test_customer_person_no_name_throw_MismatchedInputException() { File file = new File( "src/test/resources/customer_person_no_name.json" ); MismatchedInputException expectedException = assertThrows(MismatchedInputException. class , () -> ob.readValue(file, Customer. class )); assertTrue(expectedException.getMessage().contains( "Missing required creator property 'person_name' " )); } @Test void test_customer_wo_email_personName_throws_MismatchedInputException() { File file = new File( "src/test/resources/customer_no_email_no_personName.json" ); MismatchedInputException expectedException = assertThrows(MismatchedInputException. class , () -> ob.readValue(file, Customer. class )); assertTrue(expectedException.getMessage().contains( "Missing required creator property 'person_name' (index 0)" )); } @Test void test_JsonProperty_field() { Customer customer = new Customer( "test@test.com" ); customer.setDataType( "major" ); customer.setId( 2 ); Order order = new Order( "test" , 10 ); customer.addOrder(order); Order order2 = new Order( "PS" , 20 ); customer.addOrder(order2); try { String jsonStr = ob.writerWithDefaultPrettyPrinter().writeValueAsString(customer); System.out.println(jsonStr); Customer readCust = ob.readValue(jsonStr, Customer. class ); assertTrue(customer.equals(readCust)); assertEquals( "major" , readCust.getDataType()); assertEquals( "test@test.com" , readCust.getEmail()); assertEquals( 2 , readCust.getId()); assertEquals( 2 , readCust.getOrders().size()); assertEquals( "test" , readCust.getOrders().get( 0 ).getProductName()); assertEquals( 10 , readCust.getOrders().get( 0 ).getQuantity()); } catch (JsonProcessingException e) { e.printStackTrace(); } } } |
- Line 26: see
customer_full.json
at step 6.1. - Line 45: see
customer_no_email.json
at step 6.2. - Line 57: see
customer_no_person.json
at step 6.4. - Line 69: see
customer_null_email.json
at step 6.5. - Line 81: see
customer_person_no_name.json
at step 6.6. - Line 90: see
customer_no_email_no_personName.json
at step 6.3.
6.1 customer_full.json
In this step, I will create a customer_full.json
file which contains all the necessary data properties. The file is used at the test_customer_full_happyPath
method.
customer_full.json
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | { "id" : 2, "person" : { "person_name":"Mary", "age":50}, "orders" : [ { "quantity" : 10, "product_name" : "test" }, { "quantity" : 20, "product_name" : "PS" } ], "type" : "major", "email" : "test@test.com" } |
- Line 4: the
person_name
property maps toPerson.name
. - Line 8,11: the
product_name
property maps toOrder.productName
. - Line 13: the
type
property maps toCustomer.dataType
.
6.2 customer_no_email.json
In this step, I will create a customer_no_email
.json file that contains a customer JSON without the required email
property. It’s used at test_customer_no_email_throw_JsonMappingException
.
customer_no_email.json
01 02 03 04 05 06 07 08 09 10 11 12 13 | { "id" : 2, "person" : null, "orders" : [ { "quantity" : 10, "product_name" : "test" }, { "quantity" : 20, "product_name" : "PS" } ], "type" : "major", "email" : null } |
- Line 12: when the
email
property isnull
, it will throw aJsonMappingException
as it is@NonNull
at step 3.1.
6.3 customer_no_email_no_personName.json
In this step, I will create a customer_no_email_no_personName.json
file that contains a customer JSON without the required email
property and person_name
.
customer_no_email_no_personName.json
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | { "id" : 2, "person" : { "name":"Mary", "age":50}, "orders" : [ { "quantity" : 10, "product_name" : "test" }, { "quantity" : 20, "product_name" : "PS" } ], "type" : "major" } |
- Line 4: the
perons
.name
should beperson_name
based on the@JsonProperty
at step 3.3.
6.4 customer_no_person.json
In this step, I will create a customer_no_person.json
file that contains a customer JSON without a person
.
customer_no_person.json
01 02 03 04 05 06 07 08 09 10 11 12 | { "id" : 2, "orders" : [ { "quantity" : 10, "product_name" : "test" }, { "quantity" : 20, "product_name" : "PS" } ], "type" : "major", "email" : "test@test.com" } |
- It should map to a
customer
object without any issue as seen intest_customer_no_person_is_ok
.
6.5 customer_null_email.json
In this step, I will create a customer_null_email.json
file that contains a custom JSON with null
email
property.
customer_null_email.json
01 02 03 04 05 06 07 08 09 10 11 12 13 | { "id" : 2, "person" : null, "orders" : [ { "quantity" : 10, "product_name" : "test" }, { "quantity" : 20, "product_name" : "PS" } ], "type" : "major", "email" : null } |
- Line 12: it throws a
JsonMappingException
as the@NonNull email
isnull
.
6.6 customer_person_no_name.json
In this step, I will create a customer_person_no_name.json
file that contains a JSON for the customer without the person’s name
property.
customer_person_no_name.json
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | { "id": 2, "person": { "name": "test", "age": 30 }, "orders": [ { "quantity": 10, "product_name": "test" }, { "quantity": 20, "product_name": "PS" } ], "type": "major", "email": "test@test.com" } |
- Line 4: it throws
MismatchedInputException
as thePerson's name
should beperson_name
as defined at step 3.3.
Run the Junit tests and all passed.
7. Conclusion
As you see in this example, I demonstrated how to serialize and deserialize via Jackson custom field name with @JsonProperty
annotation. We can annotate Java POJO fields with the @JsonProperty
annotation at the field
, getter
, or constructor
to map the Java fields to JSON properties.
Please note that there are two Jackson library versions: version 2 is provided by fasterxml and version 1 is provided by codehaus. Make sure that @JsonProperty
is imported from the correct package if the project contains both versions of Jackson libraries.
8. Download
This was an example of a Maven project which demonstrated how to customize field name with the @JsonpPoperty
annotation.
You can download the full source code of this example here: Custom field name with @JsonProperty