Jackson is a great tool to have in your tool set if you deal with JSON or XML. It facilitates easy serialization and de-serialization to and from Java classes with a convenient annotation based interface. With the same set of annotations, we can achieve both XML and JSON serialization and de-serialization. With Jackson’s data-format-xml
it is even possible to give the same Class a different JSON and XML representation.
@JacksonXmlRootElement(localName = "user-account")
@JsonRootName("user")
public class Account {
private String name;
private String emailAddress;
@JsonProperty("name")
@JacksonXmlProperty(localName = "name")
public String getName() {
return name;
}
@JsonProperty("email_address")
@JacksonXmlProperty(localName = "email-address")
public String getEmailAddress() {
return emailAddress;
}
// ...
}
When this gets used, it does the serialization and de-serializaion to and from XML and JSON in different forms:
jsonMapper = new ObjectMapper();
jsonMapper.configure(WRAP_ROOT_VALUE, true);
jsonMapper.configure(UNWRAP_ROOT_VALUE, true);
xmlMapper = new XmlMapper();
// ...
Account account = new Account("John Doe", "[email protected]");
String jsonString = jsonMapper.writeValueAsString(account);
System.out.println(jsonString);
Account deSerializedAccount = jsonMapper.readValue(jsonString, Account.class);
assertEquals(account, deSerializedAccount);
String xmlString = xmlMapper.writeValueAsString(account);
System.out.println(xmlString);
deSerializedAccount = xmlMapper.readValue(xmlString, Account.class);
assertEquals(account, deSerializedAccount);
{"user":{"name":"John Doe","email_address":"[email protected]"}}
<user-account><name>John Doe</name><email-address>[email protected]</email-address></user-account>
Things get really interesting when we introduce FreeBuilder. FreeBuilder supports Jackson and we will be able to do serialization correctly. However, XML de-serialization does not work as expected.
cksonXmlRootElement(localName = "user-account")
@JsonRootName("user")
@FreeBuilder
@JsonDeserialize(builder = Account.Builder.class)
public interface Account {
@JsonProperty("name")
@JacksonXmlProperty(localName = "name")
String getName();
@JsonProperty("email_address")
@JacksonXmlProperty(localName = "email-address")
String getEmailAddress();
class Builder extends Account_Builder {}
}
Account account = new Account.Builder()
.setEmailAddress("[email protected]")
.setName("John Doe")
.build();
String jsonString = jsonMapper.writeValueAsString(account);
System.out.println(jsonString);
Account deSerializedAccount = jsonMapper.readValue(jsonString, Account.class);
assertEquals(account, deSerializedAccount);
String xmlString = xmlMapper.writeValueAsString(account);
System.out.println(xmlString);
deSerializedAccount = xmlMapper.readValue(xmlString, Account.class);
assertEquals(account, deSerializedAccount);
This will cause Jackson to throw an error while de-serializing XML, even though de-serializing to JSON works as expected.
{"user":{"name":"John Doe","email_address":"[email protected]"}}
<user-account><name>John Doe</name><email-address>[email protected]</email-address></user-account>
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "email-address" (class in.sdqali.json.Account$Builder), not marked as ignorable (3 known properties: "emailAddress", "email_address", "name"])
at [Source: (StringReader); line: 1, column: 83] (through reference chain: in.sdqali.json.Account$Builder["email-address"])
After digging around, it turns out that FreeBuilder keeps only the @JsonProperty
annotation on methods that it finds. This in turn causes the object created by the builder to have methods whose @JacksonXmlProperty
annotations are stripped of, which in turn causes Jackson to look for the camel-cased versions of the attribute names. I have opened a new GitHub issue for this.
Until this is resolved, if you use FreeBuilder and need to have different XML and JSON representation, you will have to write a custom Jackson Serializer and Deserializer.