Is it possible to use Jackson to deserialize a value class (final, no setters) that only has an all args constructor and a Builder? I can't use the JsonDeserialize and JsonPOJOBuilder since I am trying to deserialize a model defined in a client library, so I cannot add the annotations. Can I specify the builder to use another way?
You can try using MixIn.
I have created one sample for your use case:
Original class:
final class Sample {
final int id;
Sample(int id) {
this.id = id;
}
}
MixIn (provide non-args constructor with same args):
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
abstract class SampleMixin {
#JsonCreator
public SampleMixin(#JsonProperty("id") int id) {
}
}
Deserilaization:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Sample.class, SampleMixin.class);
Sample sample = mapper.readValue(json, Sample.class);
You can. Builder must meet certain requirements. For instance its methods for fields must have certain prefix, like "with" or "set".
Here is DTO class and its builder without any jackson annotations:
public class DtoBuilderWithFinalFieldsWithoutJackson {
public final String stringValue;
private DtoBuilderWithFinalFieldsWithoutJackson(final String stringValue){
this.stringValue = stringValue;
}
public static Builder builder(){
return new Builder();
}
public static class Builder {
private String stringValue;
public Builder withStringValue(String stringValue) {
this.stringValue = stringValue;
return this;
}
public DtoBuilderWithFinalFieldsWithoutJackson build() {
return new DtoBuilderWithFinalFieldsWithoutJackson(stringValue);
}
}
}
Without any additional effort with a default custom object you can serialize this dto object. You are responsible for instance creation. Jackson only needs to access fields. In our case this is a public field.
In case DTO is used for deserialization, you need to customize custom object:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector(){
#Override
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
//set analogue of: #JsonDeserialize(builder = DtoBuilderWithFinalFieldsWithoutJackson.Builder.class)
if (DtoBuilderWithFinalFieldsWithoutJackson.class.equals(ac.getRawType())) {
return DtoBuilderWithFinalFieldsWithoutJackson.Builder.class;
} else {
return super.findPOJOBuilder(ac);
}
}
#Override
public JsonPOJOBuilder.Value findPOJOBuilderConfig(AnnotatedClass ac) {
if (DtoBuilderWithFinalFieldsWithoutJackson.class.equals(ac.getRawType())) {
//both build and with - are default in this case:
//set analogue of #JsonPOJOBuilder(buildMethodName = "build", withPrefix = "with")
return new JsonPOJOBuilder.Value("build", "with");
} else {
return super.findPOJOBuilderConfig(ac);
}
}
});
and use this customized CustomObject in your implementations. Here is a test and full example can be found here: DtoBuilderWithFinalFieldsWithoutJackson test
Related
I have a class:
public class User extends Body {
private Integer userId;
private String userName;
private String emailId;
//getters and setters
}
I want to exclude Body class properties with Jackson mapper because I get an error.
ObjectMapper mapper = new ObjectMapper();
User user = new User;
String jsonString = mapper.writeValueAsString(user);
How I can convert object to JSON with excluding all extended or implementation class? I need convert only User class without Body
My super class has many public method like this:
public final Enumeration method(String email) {
throw new RuntimeException("Error");
}
public final Object method(String name) {
throw new RuntimeException("Error");
}
1. Using #JsonView annotation
Jackson library has #JsonView annotation which allows to provide different views of the serialized class.
You need to create a class describing different views like this:
public class Views {
public interface Base {} // view of Base class properties
public interface Child {} // view of Child class properties (i.e. User)
}
Then you mark up the fields/getters in the base Body class with #JsonView(Views.Base.class):
public class Body {
#JsonView(Views.Base.class)
private int foo;
#JsonView(Views.Base.class)
public String getBar() {
return "bar";
}
// other getters/setters
}
The User class can be marked at class level:
#JsonView(Views.Child.class)
public class User extends Body {
private Integer userId;
private String userName;
private String email;
// getters/setters
}
And when serializing with ObjectMapper you set its writer up to use specific view writerWithView:
ObjectMapper mapper = new ObjectMapper();//
User user = new User(1, "Jack", "jack#company.com");
String json = mapper.writerWithView(Views.Child.class).writeValueAsString(user);
System.out.println("custom view: " + json);
System.out.println("full view: " + mapper.writeValueAsString(user));
Output:
custom view: {"userId":1,"name":"Jack","email":"jack#company.com"}
full view: {"foo":0,"userId":1,"name":"Jack","email":"jack#company.com","bar":"bar"}
2. Using #JsonIgnoreProperties annotation
It is also possible to customize the view of the child class by ignoring its parent class' properties:
#JsonIgnoreProperties({"foo", "bar"})
public class User extends Body {
private Integer userId;
private String name;
private String email;
}
Then there's no need to configure the writer ObjectMapper instance:
System.out.println("base class fields ignored: " + mapper.writeValueAsString(user));
Output:
base class fields ignored: {"userId":1,"name":"Jack","email":"jack#company.com"}
3. Configure ObjectMapper to set custom JacksonAnnotationIntrospector
It is also possible to configure the ObjectMapper instance to set a custom annotation introspector to completely ignore properties belonging to the parent Body class:
// imports for Jackson v.2.x
// import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
// import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
// imports for Jackson v.1.9
import org.codehaus.jackson.map.introspect.AnnotatedMember;
import org.codehaus.jackson.map.introspect.JacksonAnnotationIntrospector;
class IgnoreBodyClassIntrospector extends JacksonAnnotationIntrospector {
#Override
public boolean hasIgnoreMarker(final AnnotatedMember member) {
return member.getDeclaringClass() == Body.class || super.hasIgnoreMarker(member);
}
}
Configure ObjectMapper and serialize User without any code changes to Body and User:
ObjectMapper mapper = new ObjectMapper()
.setAnnotationIntrospector(new IgnoreBodyClassIntrospector());
User user = new User(3, "Nobody", "nobody#company.com");
System.out.println("no base class fields: " + mapper.writeValueAsString(user));
Output:
no base class fields: {"userId":3,"name":"Nobody","email":"nobody#company.com"}
Serializing
public class Subclass extends Superclass {
private static final long serialVersionUID = 1L;
private int someProperty;
public Subclass() {
}
public Subclass(int someProperty, String myProperty) {
super(myProperty);
this.someProperty = someProperty;
}
public int getSomeProperty() {
return someProperty;
}
public void setSomeProperty(int someProperty) {
this.someProperty = someProperty;
}
}
with
public class Superclass implements Serializable {
private static final long serialVersionUID = 1L;
private String myProperty;
public Superclass() {
}
public Superclass(String myProperty) {
this.myProperty = myProperty;
}
public String getMyProperty() {
return myProperty;
}
public void setMyProperty(String myProperty) {
this.myProperty = myProperty;
}
}
shouldn't fail because of
Exception in thread "main" java.lang.IllegalArgumentException: class richtercloud.gson.exclusion.strategy.Subclass declares multiple JSON fields named serialVersionUID
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:170)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:100)
at com.google.gson.Gson.getAdapter(Gson.java:423)
at com.google.gson.Gson.toJson(Gson.java:661)
at com.google.gson.Gson.toJson(Gson.java:648)
at com.google.gson.Gson.toJson(Gson.java:603)
at com.google.gson.Gson.toJson(Gson.java:583)
at richtercloud.gson.exclusion.strategy.Main.main(Main.java:41)
if a serialization exclusion strategy is used as follows:
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.addSerializationExclusionStrategy(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
boolean retValue = f.getName().equals("serialVersionUID");
return retValue;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.create();
Subclass a = new Subclass();
String response = gson.toJson(a);
System.out.println(response);
I'm using Gson 2.8.2.
Analysis
It seems, this is a known issue:
Integrate with java.sql.ResultSet · Issue #464 · google/gson · GitHub.
error happend when converting object to json · Issue #399 · google/gson · GitHub.
Some alternative solutions
Solution #1: Use type adapter
The recommended solution there (see the «Analysis» links), the workaround, is to introduce an appropriate type adapter.
Solution #2: Exclude static fields from serialization
Why not exclude the serialization of the static fields at all?
new GsonBuilder()
// ...
.excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC)
// ...
Solution #3: Serialize #Exposed fields only
It is possible to explicitly expose serializable fields by using the #Exposed annotation and configure the GsonBuilder appropriately:
new GsonBuilder()
// ...
.excludeFieldsWithoutExposeAnnotation()
// ...
The problem is that you are only calling addSerializationExclusionStrategy, but when Gson builds the internal reflection-based adapter it considers serialization and deserialization. Therefore you must also use GsonBuilder.addDeserializationExclusionStrategy to register the same exclusion strategy for deserialization.
I'm working on a project that requires me to serialize and deserialize generic objects. The way I'm going about this, is defining an abstract class Serializer that implements a toBytes() and a static fromBytes(). All is well with this approach, as I can pass an object instance to a generic class Foo that expects a Serializer subclass, and I can ensure the object knows how to serialize and deserialize itself.
Now my question. Java serialization kinda sucks. I have multiple implementations I'd like to try swapping in and out, and ultimately I'd like the user to be able to decide the format. How would I go about changing the implementation details of Serializer? I know I can't override static methods, so how would I do this without decoupling Foo and Serializer and not being able to ensure my generic object has the appropriate toBytes() and fromBytes() method in Foo?
Here is code if anyone is confused:
public abstract class Serializer {
public static Serializer fromBytes(byte[] bytes) {
...
}
public byte[] toBytes() {
...
}
}
public class Foo<T extends Serializer> {
private T t;
public Foo(T t) {
this.t = t;
}
public void foo() {
t.toBytes(); //this will polymorph into the correct call because it's called on the object instance and not the Serializer class
}
public void bar(byte[] bytes) {
T.fromBytes(bytes); // I'd like to be able to override this method so I can use different implementations
}
}
I'm not sure if this is a good approach, but how about using Jackson library and serialize your object as a json node? for example:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = SoundFile.class, name = "sound"),
#Type(value = VideoFile.class, name = "video")
})
abstract class File{
private String id;
private String type;
#JsonCreator
public File(#JsonProperty("id") String id)
{
this.id=id;
}
public String getId() {return this.id;}
public abstract String getType();
}
class SoundFile extends File{
#JsonCreator
public SoundFile(#JsonProperty("id") String id) {
super(id);
}
#Override
public String getType() {
return "sound";
}
}
class VideoFile extends File{
#JsonCreator
public VideoFile(#JsonProperty("id") String id) {
super(id);
}
#Override
public String getType() {
return "video";
}
}
public class GenericApp {
public static void main(String[] args) {
ObjectMapper om = new ObjectMapper();
List<File> files = Arrays.asList(new VideoFile("1"),new SoundFile("2"));
//serialize
List<byte[]> fileSerialized = files.stream().map(file->{
try {
return om.writeValueAsBytes(file);
}catch(Exception e) {return null;}
}).collect(Collectors.toList());
//de-serialize
List<File> filesDeSerialized = fileSerialized.stream().map(bytes ->{
try {
return om.readValue(bytes, File.class);
}
catch(Exception e) {return null;}
}).collect(Collectors.toList());
filesDeSerialized.stream().forEach(file->{
System.out.println("id :"+file.getId()+" - "+file.getClass());
});
}
}
this would properly deserialize these objects and print:
id :1 - class com.dsncode.stackoverflow.VideoFile
id :2 - class com.dsncode.stackoverflow.SoundFile
however, you should define a #JsonTypeInfo and a #JsonSubType for all your sub-classes of your Generic Type. Because, by indicating this field, you will indicate to Jackson deserializer, which class should create for your generic type.
For some reason I am not able to hide protected fields (without setter), via ObjectMapper configuration, from being serialized to a JSON string.
My POJO:
public class Item {
protected String sn;
private String name;
public Item(){
sn = "43254667";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSn() {
return sn;
}
}
My mapper:
mapper.setVisibility(PropertyAccessor.SETTER, Visibility.PUBLIC_ONLY);
mapper.setVisibility(PropertyAccessor.GETTER, Visibility.PUBLIC_ONLY);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.NONE);
The output is:
{
"sn" : "43254667",
"name" : "abc"
}
UPDATE: I cannot modify the Item class, hence I cannot use annotations.
Use #JsonIgnore
You could annotate the field or method with #JsonIgnore.
It's a marker annotation that indicates that the annotated method or field is to be ignored by introspection-based serialization and deserialization functionality.
Use as following:
public class Item {
#JsonIgnore
protected String sn;
...
}
Or as following:
public class Item {
...
#JsonIgnore
public String getSn() {
return sn;
}
}
Use #JsonIgnore with mix-ins
Based on your comment, you could use mix-in annotations when modifying the classes is not an option, as described in this answer.
You can think of it as kind of aspect-oriented way of adding more annotations during runtime, to augment statically defined ones.
First, define a mix-in annotation interface (class would do as well):
public interface ItemMixIn {
#JsonIgnore
String getSn();
}
Then configure your ObjectMapper to use the defined interface as a mix-in for your POJO:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Item.class, ItemMixIn.class);
For extra details, check the documentation.
Use a BeanSerializerModifier
Based on your comment, you may consider a BeanSerializerModifier, as following:
public class CustomSerializerModifier extends BeanSerializerModifier {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
// In this method you can add, remove or replace any of passed properties
return beanProperties;
}
}
Then register the custom serializer as a module in your ObjectMapper.
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimpleModule() {
#Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(new CustomSerializerModifier());
}
});
You've instructed your mapper to serialize public getters:
mapper.setVisibility(PropertyAccessor.GETTER, Visibility.PUBLIC_ONLY);
That's why Jackson will serialize the sn field (You have a public getter here in the end).
To get rid of the serialized sn field, simply annotate your getter with #JsonIgnore:
public class Item{
protected String sn;
private String name;
public Item(){
sn = "43254667";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonIgnore
public String getSn() {
return sn;
}
}
If you cannot annotate your class, you can always write a custom serializer for your POJO or use Mixins
I'm trying to deserialize JSON Array, which is persisted into my MongoDB, to a Java object by using Jackson. I found many tutorials mentioned to handle this polymorphism by adding:
#JsonTypeInfo(use=Id.CLASS,property="_class")
to a Super-class. However, in my case, I can't be able to modify the Super-class. So, are there some solutions to solve it without modifying the Super-class? Here is my code:
public class User {
#JsonProperty("_id")
private String id;
private List<Identity> identities; // <-- My List contains objects of an abstract class; Identity
public User(){
identities = new ArrayList<Identity>();
}
public static Iterable<User> findAllUsers(){
return users().find().as(User.class); // Always give me the errors
}
/*More code*/
}
It always give me the error - Can not construct instance of securesocial.core.Identity, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information.
You can use #JsonDeserilize annotation to bind a concrete implementation class to an abstract class. If you cannot modify your abstract class you can use the Jackson Mix-in annotations to tell Jackson how to find the implementation class.
Here is an example:
public class JacksonAbstract {
public static class User {
private final String id;
private final List<Identity> identities;
#JsonCreator
public User(#JsonProperty("_id") String id, #JsonProperty("identities") List<Identity> identities) {
this.id = id;
this.identities = identities;
}
#JsonProperty("_id")
public String getId() {
return id;
}
public List<Identity> getIdentities() {
return identities;
}
}
public static abstract class Identity {
public abstract String getField();
}
#JsonDeserialize(as = IdentityImpl.class)
public static abstract class IdentityMixIn {
}
public static class IdentityImpl extends Identity {
private final String field;
public IdentityImpl(#JsonProperty("field") String field) {
this.field = field;
}
#Override
public String getField() {
return field;
}
}
public static void main(String[] args) throws IOException {
User u = new User("myId", Collections.<Identity>singletonList(new IdentityImpl("myField")));
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Identity.class, IdentityMixIn.class);
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(u);
System.out.println(json);
System.out.println(mapper.readValue(json, User.class));
}
}