Java Jackson Serialization ignore specific nested properties with Annotations - java

I'm using jackson (with spring boot) to return some DTOs like json. The problem is that I have specific DTO which contains nested objects, which contains another objects. Can I have ignore some nested properties directly from the DTO, without any Annottations on the nested objects (because they are used in another DTOs)
public class MyDTO {
private MyObjectA a;
}
public class MyObjectA a {
private MyNestedObject b;
}
I want when i serialize MyDTO to exclude MyNestedObject b
I've tried with #JsonIgnoreProperties, but it not works with nested objects.
Can I achieve this mission only with Annotations in the MyDTO class?

You might use #JsonView. You need to annotate some nested objects but it is not kind a static thing that then hides everything from other DTOs or so.
For example you could declare following views to use:
public class View {
public static class AllButMyNestedObject {
}
public static class AlsoMyNestedObject
extends AllButMyNestedObject {
}
}
Then annotating your classes like:
#JsonView(AllButMyNestedObject.class)
public class MyDTO {
private MyObjectA a;
}
and
public class MyObjectA {
#JsonView(AlsoMyNestedObject.class)
private MyNestedObject b;
}
you can decide with mapper what to serialize, like:
ObjectMapper mapper = new ObjectMapper();
String result = mapper
.writerWithView(View.AlsoMyNestedObject.class)
// OR .writerWithView(View.AllButNestedObject.class)
.writeValueAsString(myDto);

Related

How to use Jackson to deserialize external Lombok builder class

I have a 3rd party Lombok builder POJO, one that I cannot modify, that I want to serialize using jackson. Notably it does not have a NoArgsConstructor.
#Data
#Builder
public class ExternalClass {
private String name;
private String data;
// etc.
}
On the surface this would appear to be simple, but it is incredibly frustrating in practice as each possible option seems to be counteracted by a different complication. In essence, I'm having trouble getting an external Lombok builder to work with a jackson mixin.
Lombok produces fluent setters of the style .name(String name) while Jackson's built-in builder deserializer expects .withName(String name). Lombok documentation, and recipes elsewhere such as here suggest using #JsonDeserialize(builder=ExternalClass.ExternalClassBuilder.class) in conjunction with #JsonPOJOBuilder(withPrefix="") on a predeclared inner stub builder. But this is not possible because the Lombok class is in an external library.
Applying these annotations to a mixin has no effect.
#JsonDeserialize(ExternalClass.ExternalClassBuilder.class)
public abstract class ExternalClassMixin {
#JsonPOJOBuilder(withPrefix="")
public static ExternalClassBuilder {
}
}
The only approach I've found that works is to leverage the package-access AllArgsConstructor created by #Builder and populate the mixin with the following constructor
public abstract class ExternalClassMixin {
#JsonCreator public ExternalClassMixin(
#JsonProperty("name") String name,
#JsonProperty("data") String data,
// etc.
) {}
}
This is obviously not desirable as it requires iterating and hard-coding every class property explicitly, making the mixin fragile to any change in the external POJO.
My question is - is there a robust, maintainable way to serialize this external builder class using Jackson without modifying it, using either a mixin or maybe a full blown deserializer?
Update
I implemented the excellent answer by #jan-rieke, including the suggestion to use reflection to seek out the inner builder class.
...
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
Class<?> innerBuilder;
try {
innerBuilder = Class.forName(ac.getName()+"$"+ac.getRawType().getSimpleName()+"Builder");
log.info("Builder found: {}", ac.getName());
return innerBuilder;
} catch( ClassNotFoundException e ) {
return super.findPOJOBuilder(ac);
}
}
You can customize your ObjectMapper as follows:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
if (ExternalClass.class.equals(ac.getRawType())) {
return ExternalClass.ExternalClassBuilder.class;
}
return super.findPOJOBuilder(ac);
}
#Override
public Value findPOJOBuilderConfig(AnnotatedClass ac) {
if (ac.hasAnnotation(JsonPOJOBuilder.class)) {
return super.findPOJOBuilderConfig(ac);
}
return new JsonPOJOBuilder.Value("build", "");
}
});
This will
explicitly configure that deserialization for ExternalClass uses its builder, and
set the default prefix for builder setter methods to "" (except when the #JsonPOJOBuilder annotation is present).
If you do not want to list all external classes explicitly in findPOJOBuilder(), you can of course programmatically look into the class to check whether it has a inner class that looks like a builder.
This can be accomplished by creating two mixins: one for ExternalClass (specifying the builder to use) and one for ExternalClass.ExternalClassBuilder (specifying the lack of a prefix in the builder methods).
#JsonDeserialize(builder = ExternalClass.ExternalClassBuilder.class)
public interface ExternalClassMixin {
}
#JsonPOJOBuilder(withPrefix="")
public interface ExternalClassBuilderMixin {
}
This serializes and deserializes the JSON in the desired manner:
String json = "{\"name\": \"The Name\", \"data\": \"The Data\"}";
ObjectMapper mapper = new ObjectMapper()
.addMixIn(ExternalClass.class, ExternalClassMixin.class)
.addMixIn(ExternalClass.ExternalClassBuilder.class, ExternalClassBuilderMixin.class);
System.out.println(mapper.readValue(json, ExternalClass.class));
System.out.println(mapper.writeValueAsString(mapper.readValue(json, ExternalClass.class)));
Output:
ExternalClass(name=The Name, data=The Data)
{"name":"The Name","data":"The Data"}

serialize and deserialize without map name

I have the following Object:
public class Class_a{
private List<class_b> someList;
}
public class Class_b{
private Map<String,String> someMap;
}
My json will look like this:
"someList":[{"someMap":{"strKey1":"strValue1"}},{"someMap":{"strKey2":"strValue2"}}]
Is it possible to serialize a Json that will look like this, without changing my Objects (and I will have the option to deserialize the Object):
"someList":[{"strKey1":"strValue1"},{"strKey2":"strValue2"}]
*I know that if will defined my object like this:
public class Class_a{
private List<Map<Strung,String>> someList;
}
i will get a Json like I want - but I am trying to find more elegant solution then 'list' that contain a 'map'
My project use spring framework and Jackson.
This worked for me I only had to add getters and setters to your classes and I was able to parse with jackson:
#Test
public void t() throws IOException {
String json = "{\"someList\":[{\"someMap\":{\"strKey1\":\"strValue1\"}},{\"someMap\":{\"strKey2\":\"strValue2\"}}]}";
Class_a a = new ObjectMapper().readValue(json, Class_a.class);
System.out.println(a);
}
#Getter
#Setter
public class Class_a{
private List<Class_b> someList;
}
#Getter
#Setter
public class Class_b{
private Map<String,String> someMap;
}
I'm using lombok but that's nothing special, you can create the getters/setters manually too and will work

How to serialize differently the same properties of the same entity using jackson

Suppose you have this entity:
class Foo{
String propA;
String propB;
}
and you want to serialize for one API like :
{propA: "ola",
propB: "Holla"}
and for another API like :
{fooPropA: "ola",
fooPropB: "Holla"}
How can this be achieved using jackson and using the same entity. Creating 2 different entities is not an option :)
There are several ways in which you can achieve this. You can enable a custom serializer (already covered by #se_vedem), register an annotation introspector which changes the property names for the corresponding class and so on.
However, if you are willing to only add a string prefix to all the property names, then the Jackson property name strategy is probably the best fit. The naming strategy class has the access to the serialized object type information, so you can make a decision whether to change the property name or not.
Here is an example using a custom annotation that defines the prefix:
public class JacksonNameStrategy {
#Retention(RetentionPolicy.RUNTIME)
public static #interface PropertyPrefix {
String value();
}
#PropertyPrefix("foo_")
public static class Foo {
public String propA;
public String propB;
public Foo(String propA, String propB) {
this.propA = propA;
this.propB = propB;
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new MyPropertyNamingStrategyBase());
System.out.println(mapper.writeValueAsString(new Foo("old", "Holla")));
}
private static class MyPropertyNamingStrategyBase extends PropertyNamingStrategy {
#Override
public String nameForField(MapperConfig<?> config,
AnnotatedField field,
String defaultName) {
PropertyPrefix ann = field.getDeclaringClass().getAnnotation(PropertyPrefix.class);
if (ann != null) {
return ann.value() + defaultName;
}
return super.nameForField(config, field, defaultName);
}
}
}
Output:
{"foo_propA":"old","foo_propB":"Holla"}
In your API method you choose between two ObjectMapper instances one with the default naming naming strategy and one with the custom one.
You can achieve this by using modules feature from Jackson.
Basically, each API would have it's own ObjectMapper and they will be configured with different modules. This way you can create 2 serializers for the same class and register them on the appropriate module. More read can be found here http://wiki.fasterxml.com/JacksonFeatureModules
However, be aware that serializers are loaded in a particular order. First it tries to get the annotated ones, if none is found it will try to get those registered from modules. So, for example if you have your class annotated with serializer, then that serializer(FooSerializer) would be chosen instead of the one configured in module(MySecondFooSerializer).
#JsonSerialize(using = FooSerializer.class)
class Foo{
String propA;
String propB;
}
module.addSerializer(Foo.class, new MySecondFooSerializer());

JSON object mapper: avoid listing derived class in serialized JSON string

Here is my class structure:
public class Business {
public long id;
public List<Employee> employees;
// constructors, getters, setters
}
public class BusinessParcelable extends Business implements Parcelable {
// some functions, not additional fields
}
public class Employee {
// fields, constructor, getters, setters
}
public class EmployeeParcelable extends Employee implements Parcelable {
// some functions, no additional fields
}
If I serialize it into a JSON string using the following code:
someFunction(BusinessParcelable b) {
ObjectMapper objectMapper = new ObjectMapper();
Business base = (Business)b;
String jsonString = objectMapper.writeValueAsString(base);
}
The jsonString looks like:
{
"id": 15,
"employees": [
{
...
}
],
"employeesParcelable": [
{
...
}
]
}
I do not want employees and employeesParcelable duplicated in my JSON string. I am able to solve it by replacing:
Business base = (Business)b;
With:
Business base = new Business();
base.setXXX(b.getXXX());
base.setYYY(b.getYYY());
base.setZZZ(b.getZZZ());
But I want to avoid this deep copy as my class structure has a lot of fields and is multiple levels deep. Is there are way in jackson to avoid duplicating the base and derived list (through some simple annotations)?
Edit:
I was just able to get the desired result using Google Gson:
Gson gson = new Gson();
String jsonUsingGson = gson.toJson(base);
It would still be good to know how to get the same result using Jackson though.
I think, that your Parcelable interface contains getEmployeesParcelable method. By default Jackson serializes all getters. To avoid this, you can annotate your method in this way:
interface Parcelable {
#JsonIgnore
List<Employee> getEmployeesParcelable();
//other methods
}

How do I give type hints to the Jackson deserializer?

I'm using Jackson as a tool to declare some objects whose classes I can't annotate (or modify at all). One of the classes has a setter and getter for an untyped list. Here's a sanitized version:
public class Family {
private List members;
public List getMembers() { return members; }
public void setMembers(List members) { this.members = members; }
//...many, many other properties
}
public class Member {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Here's the JSON I'm trying to deserialize:
{ "members" : [ { "name" : "Mark" } ] }
The naive code I would use is this:
ObjectMapper mapper = new ObjectMapper();
Family family = mapper.readValue(json, Family.class);
Member member = (Member) family.getMembers().get(0);
System.out.println(member.getName());
But of course this fails, as Jackson did not know to create a list of Members instead of its fallback, a list of LinkedHashMaps.
What's the easiest way to instruct Jackson to treat members as a List<Member>? I don't think I want to use a fully custom deserializer for the class, since there are many other properties that Jackson handles fine.
Here's the best I could come up with, using BeanDeserializerModifier:
mapper.setDeserializerProvider(new StdDeserializerProvider()
.withDeserializerModifier(new BeanDeserializerModifier() {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BasicBeanDescription beanDesc, BeanDeserializerBuilder builder) {
if (beanDesc.getBeanClass() == Family.class) {
CollectionType type = CollectionType.construct(ArrayList.class, SimpleType.construct(Member.class));
TypeDeserializer typeDeserializer = type.getTypeHandler();
SettableBeanProperty.MethodProperty membersProperty = (SettableBeanProperty.MethodProperty) builder.removeProperty("members");
builder.addProperty(new SettableBeanProperty.MethodProperty(
"members",
type,
typeDeserializer,
beanDesc.getClassAnnotations(),
(AnnotatedMethod) membersProperty.getMember()
));
}
return builder;
}}));
It works, but seems really low level (and verbose!) for what I'm trying to do. What am I missing here?
Edit
I should note, I'm using Jackson 1.8.2, but could update if there's a compelling reason to.
Mix-in annotations were the critical piece of the puzzle I was missing. Here's a much cleaner way of solving this problem:
ObjectMapper mapper = new ObjectMapper();
mapper.getDeserializationConfig().addMixInAnnotations(Family.class, FamilyMixin.class);
Family family = mapper.readValue(json, Family.class);
Member member = (Member) family.getMembers().get(0);
//...
interface FamilyMixin {
#JsonDeserialize(contentAs = Member.class)
void setMembers(List members);
}
What mix-in annotations let you do is annotate a proxy that is under your control. When that mix-in class is applied to the real class, Jackson behaves as if those annotations annotated the real class's members.
In my case, I use JsonDeserialize.contentAs() to specify the container's content type. But I believe most annotations should be available using this method.

Categories

Resources