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.
Related
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.
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.
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
I have two builders - PayloadA and PayloadB. To make example simpler, I have removed lot of other fields.
PayloadA.Builder constructor takes processName, genericRecord as an input parameter and then extract few things from genericRecord. And on that I am doing validation.
PayloadB.Builder constructor also takes processName, genericRecord as an input parameter and then it extract few different things from genericRecord as compared to above. And on those different fields I am doing validation.
As you can see, common thing between those two Payload?.Builder is processName, genericRecord, extracting oldTimestamp value and then isValid method.
Below is my PayloadA class:
public final class PayloadA {
private final String clientId;
private final String deviceId;
private final String processName;
private final GenericRecord genericRecord;
private final Long oldTimestamp;
private PayloadA(Builder builder) {
this.clientId = builder.clientId;
this.deviceId = builder.deviceId;
this.processName = builder.processName;
this.genericRecord = builder.genericRecord;
this.oldTimestamp = builder.oldTimestamp;
}
public static class Builder {
private final String processName;
private final GenericRecord genericRecord;
private final String clientId;
private final String deviceId;
private final Long oldTimestamp;
public Builder(PayloadA payload) {
this.processName = payload.processName;
this.genericRecord = payload.genericRecord;
this.clientId = payload.clientId;
this.deviceId = payload.deviceId;
this.oldTimestamp = payload.oldTimestamp;
}
public Builder(String processName, GenericRecord genericRecord) {
this.processName = processName;
this.genericRecord = genericRecord;
this.clientId = (String) DataUtils.parse(genericRecord, "clientId");
this.deviceId = (String) DataUtils.parse(genericRecord, "deviceId");
this.oldTimestamp = (Long) DataUtils.parse(genericRecord, "oldTimestamp");
}
// calling this method to validate
public boolean isValid() {
return isValidClientIdDeviceId();
}
private boolean isValidClientIdDeviceId() {
// validate here
}
public PayloadA build() {
return new PayloadA(this);
}
}
// getter here
}
Below is my PayloadB class:
public final class PayloadB {
private final GenericRecord genericRecord;
private final String processName;
private final String type;
private final String datumId;
private final Long oldTimestamp;
private PayloadB(Builder builder) {
this.processName = builder.processName;
this.genericRecord = builder.genericRecord;
this.type = builder.type;
this.datumId = builder.datumId;
this.oldTimestamp = builder.oldTimestamp;
}
public static class Builder {
private final GenericRecord genericRecord;
private final String processName;
private final String type;
private final String datumId;
private final Long oldTimestamp;
public Builder(PayloadB payload) {
this.processName = payload.processName;
this.genericRecord = payload.genericRecord;
this.type = payload.type;
this.datumId = payload.datumId;
this.oldTimestamp = payload.oldTimestamp;
}
public Builder(String processName, GenericRecord genericRecord) {
this.processName = processName;
this.genericRecord = genericRecord;
this.type = (String) DataUtils.parse(genericRecord, "type");
this.datumId = (String) DataUtils.parse(genericRecord, "datumId");
this.oldTimestamp = (Long) DataUtils.parse(genericRecord, "oldTimestamp");
}
// calling this method to validate
public boolean isValid() {
return isValidType() && isValidDatumId();
}
private boolean isValidType() {
// validate here
}
private boolean isValidDatumId() {
// validate here
}
public PayloadB build() {
return new PayloadB(this);
}
}
// getter here
}
Now is there any way I can use concept of abstract class here? I can create an abstract class Payload but what should be the stuff inside my abstract class:
public final class PayloadA extends Payload { ... }
public final class PayloadB extends Payload { ... }
And then once I build both my builder, I will pass it to some other method and there I want to access all the fields using getters. So let's say I have build PayloadA so I will send to execute method as shown below and then in that method, I want to extract all the fields of PayloadA. Similarly if I send PayloadB to execute method, then I want to extract all the fields of PayloadB class using getters. How can I do this?
private void execute(Payload payload) {
// How can I access fields of PayloadA or PayloadB
// depending on what was passe
}
Create a super class for the payloads only if the mentioned fields are not common by a coincidence. You can move common fields and methods (but not the builders) in there. You could even create a super class for the builders but it will probably clutter the code too much.
If you really have a use for the payload super class then you can implement your execute method with the Visitor Pattern:
First, you have to create a visitor where you can access your concrete classes:
public class PayloadVisitor {
public void visit(PayloadA payloadA) {
// use payload A here
}
public void visit(PayloadB payloadB) {
// use payload B here
}
}
Then you have to add a method to your super class accepting the visitor:
public abstract class Payload {
// common fields and methods
public abstract void accept(PayloadVisitor visitor);
}
Override the method accept in the subclasses:
public final class PayloadA extends Payload {
// ...
#Override
public void accept(PayloadVisitor visitor) {
visitor.visit(this);
}
}
public final class PayloadB extends Payload {
// ...
#Override
public void accept(PayloadVisitor visitor) {
visitor.visit(this);
}
}
Your method execute just redirects the call to the according visit method:
private void execute(Payload payload) {
payload.accept(new PayloadVisitor());
}
The visitor pattern can be overwhelming. You can also keep it simple and use instanceof to determine the concrete class.
I think the question here is if PayloadA and PayloadB are sharing something meaning full together for the design. If the logic is somehow the same except one parameter then you can have one class.
Maybe you can have the abstract class, and for the implementation for a specific field you can return your concrete value for a specific implementation.
For example Abstract class has abstract setter/getter for a field and when you implement that method to PayloadA and PayloadB you can return the field you want.
I think the problem is the design here not how to do it. See what your classes really are and then you have many options
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));
}
}