I am developing using an ORM where I extend a base orm class to create tables.
For example:
public class Person extends DbItem {
#JsonIgnore
private String index;
private String firstName;
private String lastName;
}
Problem is that when I use ObjectMapper to serialize, it tries to serialize the members of the DbItem class. Is there any simple way to prevent this? For example with an annotation.
I had a look at a similar problem Jackson serialization: how to ignore superclass properties but I was hoping it could be done simpler, and I'm not sure if I could do it as I can't change the superclass since it is in an external library.
You can use a Mix-in or #JsonIgnoreProperties
For the purposes of these examples, the base ORM class and extension are assumed to be:
public class DbItem {
public String dbPropertyA;
public String dbPropertyB;
}
and
public class Person extends DbItem {
public String index;
public String firstName;
public String lastName;
}
respectively.
Using a Mix-in
A Mix-in is an abstraction of the de/serialization instructions that Jackson understands from an object itself. It is a way to customize de/serialization of 3rd party classes. In order to define a Mix-in, an abstract class must be created and registered with the ObjectMapper.
Example Mix-in Definition
public abstract class PersonMixIn {
#JsonIgnore public String dbPropertyA;
#JsonIgnore public String dbPropertyB;
#JsonIgnore public String index;
}
Registering the Mix-in
#Test
public void serializePersonWithMixIn() throws JsonProcessingException {
// set up test data including parent properties
Person person = makeFakePerson();
// register the mix in
ObjectMapper om = new ObjectMapper()
.addMixIn(Person.class, PersonMixIn.class);
// translate object to JSON string using Jackson
String json = om.writeValueAsString(person);
assertFalse(json.contains("dbPropertyA"));
assertFalse(json.contains("dbPropertyB"));
assertFalse(json.contains("index"));
System.out.println(json);
}
#JsonIgnoreProperties
If you want to avoid creating a class and configuring the ObjectMapper, the #JsonIgnoreProperties annotation can be utilized. Simply annotate the class you are serializing and list the properties to exclude.
Example Serializable Object
#JsonIgnoreProperties({"index", "dbPropertyA", "dbPropertyB"})
public class Person extends DbItem {
public String index;
public String firstName;
public String lastName;
}
See It In Action
#Test
public void serializePersonWithIgnorePropertiesAnnotation() throws JsonProcessingException {
// set up test data including parent properties
Person person = makeFakePerson();
ObjectMapper om = new ObjectMapper();
// translate object to JSON string using Jackson
String json = om.writeValueAsString(person);
assertFalse(json.contains("dbPropertyA"));
assertFalse(json.contains("dbPropertyB"));
assertFalse(json.contains("index"));
System.out.println(json);
}
You want to do custom field level serialization. This will be a bit more work to maintain your code base, but is by far the simplest solution. See Jackson JSON custom serialization for certain fields for implementation details.
Related
I am following this article https://quarkus.io/guides/rest-client to build a REST Client to parse the output from the restcountries.eu service.
Here the class holding the model:
public class Country {
public String name;
public String alpha2Code;
public String capital;
public List<Currency> currencies;
public static class Currency {
public String code;
public String name;
public String symbol;
}
}
Now, suppose I would like to add a custom fields such as timestamp, to record the instant when this object has been created. I imagine, I would go ahead and add another field like below:
public class Country {
public String name;
public String alpha2Code;
public String capital;
public List<Currency> currencies;
public Instant timestamp; //<--------- added attribute
[....]
My question is: how do I tell the client to populate that field? Normally, I would have done it in the constructor. However, I could not find docs that explain this part.
Thanks for your help
Simone
You can actually do this in the default constructor. Frameworks like JSONB or Jackson expect POJOs to have a default constructor. They will call it when they create an instance of Country.
Use the #JsonbTransient or #JsonIgnore annotations to mark that attribute of your POJO as ignorable in order to avoid the unmarshaller complaining about attributes that cannot be found in the response.
#Data
public class Country {
private String name;
private String alpha2Code;
private String capital;
private List<Currency> currencies;
#JsonbTransient // if you're using JSONB (default in Quarkus)
#JsonIgnore // if you're using Jackson
private Instant timestamp;
public Country() {
this.timestamp = Instant.now();
}
PS The #Data annotation is something you should consider using. Encapsulation is not a bad thing but creating getters/setters is tedious. But Project Lombok certainly helps here.
I am new to Dozer, and have done flat mapping from one POJO to another using Dozer xml mapping.But now I want to map complex POJO which is given below and I am stucked how to do it.
// -----------------------Source Classes-----------------------------
public class Source{
public String sourceId;
public Product product;
public List<Item> items;
}
public class Product{
public Integer productId;
public String productName;
}
public class Item{
public Integer id;
public Integer qty;
public String desc;
}
// -----------------------Destination Classes-------------------
public class Destination{
public String destId;
public DestProduct destProduct;
public List<DestItem> destItems;
}
public class DestProduct{
public Integer nProductId;
public String sProductName;
}
public class DestItem{
public Integer nId;
public Integer nQty;
public String sDesc;
}
How do I tell Dozer to map Source to Destination?
You should check Dozer documentation. It has everything you need to map your classes.
I think you are worried mainly for below mappings:
1. Map custom object fields and wrapper classes fields:
Check the Basic property mapping in dozer documentation. Many data type coversions are performed automatically by the Dozer mapping engine. Check the below link for more info.
http://dozer.sourceforge.net/documentation/simpleproperty.html
2. List fields containing custom object mappings:
This is explained at the below link:
http://dozer.sourceforge.net/documentation/collectionandarraymapping.html#
For cases where a feature isn't supported out of the box, you can also write a custom converter:
http://dozer.sourceforge.net/documentation/customconverter.html
Also, It will help to first write simple standalone programs to understand/test a particular mapping before jumping with implementation in your project.
Let's say I have following flat JSON structure:
{
"name": "name",
"validFrom": "2018-01-09",
"validTo": "2018-01-10",
}
and MyPojo class:
public class MyPojo {
private String name;
#JsonUnwrapped
private Validity validity;
}
and Validity class:
public class Validity {
private LocalDate validFrom;
private LocalDate validTo;
}
I created custom unwrapping serializer and it works fine.
I would like to deserialize JSON above into MyPojo class which includes Validity value object.
How should custom deserializer for Validity be implemented?
#JsonProperty does not work as I want to use 2 Json properties for Validity construction
I would recommend a constructor in this case, a lot simpler than a custom deserializer, something like:
#JsonCreator
public MyPojo(#JsonProperty("name") String name,
#JsonProperty("validFrom") String validFrom,
#JsonProperty("validTo") String validTo) {
this.name = name;
this.validity = new Validity(validFrom, validTo);
}
It's implied that LocalDate is parsed from String above but you may have Jackson parse them.
You may skip annotations above if you use Java 8 with parameter names module
That will require an extra annotation on validity, see open Jackson issue here
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
#JsonUnwrapped
private Validity validity;
The json I'm dealing with uses underscores in the property names, but I wish to keep camel case in Java. Further, I'm using immutable style POJOs, since that's a best practice our team has long adopted.
Everything works fine if I put duplicate #JsonProperty annotations in the constructor and on the getter, but this adds a lot of unnecessary bloat (in our classes, we have a couple dozen properties.) Is there a way to tell Jackson exactly once how to transform the Java property name to the JSON property name?
public class Foo {
public final String someProperty;
#JsonCreator
public Foo(#JsonProperty("some_property") someProperty) {
this.someProperty = someProperty;
}
#JsonProperty("some_property")
public String getSomeProperty() {
return someProperty;
}
}
You can choose the naming convention used for JSON. In this case you need SNAKE_CASE. It will convert someProperty field to "some_property": "" JSON. Then you don't need the #JsonProperty in the property.
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
In this case, in Foo, you need to provide the field in the constructor, what requires the #JsonProperty in the constructor params:
public class Foo {
public final String someProperty;
#JsonCreator
public Foo(#JsonProperty("some_property") String someProperty) {
this.someProperty = someProperty;
}
public String getSomeProperty() {
return someProperty;
}
}
At least you can get rid of one of the annotations.
I am using Spring #RequestBody to map a JSON payload to a Java Object. Unfortunately this JSON payload does not use a set convention but rather has names that use both camelCase and snake_case.
To be clear my Controller looks like this:
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
... code here ...
}
with the MobileDeviceData Entity object having several setter methods like:
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public void setFlagId(int flagId) {
this.flagId = flagId;
}
This works great and without any extra effort when the JSON objects name is camelCase. However for snake_case names I need to add the Annotation:
#JsonProperty("flag_id")
private int flagId;
in order for it to be picked up.
I know it's not a good idea to use the #JsonProperty if it can be avoided as you then will need to annotate every parameter. My question is, is there a more general way to enforce matching snake_case with the corresponding camelCase in the Entity object? And obviously to do it without screwing up the ones that are already camelCase.
As per the article here, there is a simple approach to deserialize the MobileDeviceData class. Here is the sample code as below:
#JsonDeserialize(using = UserDeserializer.class)
public class User {
private ObjectId id;
private String username;
private String password;
public User(ObjectId id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public ObjectId getId() { return id; }
public String getUsername() { return username; }
public String getPassword() { return password; }
}
Assume User is the class we’re interested in writing the Deserializer for. Not much is notable here, except for the annotations that tell Jackson who knows how deserialize this class.
public class UserDeserializer extends JsonDeserializer {
#Override
public User deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
return new User(null,
node.get("username").getTextValue(),
node.get("password").getTextValue());
}
}
The deserializer is created by extending Jackson’s abstract JsonDeserializer class, and giving it the type we want to deserialize to. Difficult is figuring out that you can reference the JSON by field name with the JsonParser's ObjectCodec.
I hope it helps.
Please feel free to comment if needed!
Having been working on this a bit, I now realize doing anything like what was requested would be counterproductive.
When you receive (deserialize) a JSON Object, it is generally expected that you will deliver (serialize) with the same parameters. If an implementation extracted both camelCase and underscore parameters the same way, then it would not know how to deserialize correctly later on. By following a standard convention and then using #JsonProperty for all the exceptions, it remains possible to deserialize and later deliver the JSON object just as it was received.