Serializing an object containing an interface - java

I have a class whose object I'm trying to serialize/deserialize:
public class PlanDetails implements Serializable {
private static final long serialVersionUID = -5344035164109142313L;
#JsonProperty("reserved_volumes")
private List<ReservedVolume> reservedVolumes;
private String description;
//getter, setter, toString()
ReservedVolume is defined as:
public class ReservedVolume implements Serializable {
private static final long serialVersionUID = 4157996827172339903L;
#JsonProperty("reserved_volume_projection")
private GeoShape reservedVolumeProjection;
#JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
#JsonProperty("effective_time_begin")
private Date effectiveTimeBegin;
private String notes;
private Double altitude;
// getter, setter, toString()
Now this GeoShape is an interface with 2 (maybe more in future) implementations.
How can I deserialize this? I'm expecting this object as request-body to a REST endpoint. As a List<ReservedVolume> a user should be able to send any combination of the implementations of GeoShape. Need to deserialize this to parse.
Wrote a method :
public static PlanDetails fromJson(String jsonString) {
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
return gson.fromJson(jsonString, new TypeToken<PlanDetails>() {
}.getType());
}
But getting Interface can't be instantiated! error while deserialization. Any help?

Related

Java convert object into abstract object

I am using SpringBoot with Java 1.8.
I have two objects that I would like to deep copy one to the other.
Basic structure
QuoteRequestDTO -> TravelRequirementDTO -> ItineraryDTO -> ServiceDTO
and
QuoteRequest -> TravelRequirement -> Itinerary -> Service
note: the Entity objects come from an external library I cannot change. I can change the DTO objects.
For example, I want to copy a DTO to an Entity.
DTO
public class QuoteRequestDTO {
protected TravelRequirementDTO travel;
...
and
public class TravelRequirementDTO {
protected ItineraryDTO required;
...
and
public class ItineraryDTO extends PayableDTO {
protected List<ServiceDTO> service;
...
and
public class ServiceDTO extends PayableDTO {
...
Entity
public class QuoteRequest {
protected TravelRequirement travel;
...
and
public class TravelRequirement implements Serializable {
private static final long serialVersionUID = 1L;
protected Itinerary required;
...
and
public class Itinerary extends Payable implements Serializable {
private static final long serialVersionUID = 1L;
protected List<Service> service;
...
and
public abstract class Service extends Payable implements Serializable {
private static final long serialVersionUID = 1L;
...
Copy Utility
I have tried the following:
import com.fasterxml.jackson.databind.ObjectMapper;
public class CopyUtils {
public static <T>T deepCopy(Object sourceObject, T targetObject) {
ObjectMapper mapper = getJacksonObjectMapper();
T targetBean = (T) mapper.convertValue(sourceObject, targetObject.getClass());
return targetBean;
}
private static ObjectMapper getJacksonObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
return objectMapper;
}
}
Usage:
public void getQuote(QuoteRequestDTO quoteRequestDTO) {
QuoteRequest quoteRequest = new QuoteRequest();
quoteRequest = CopyUtils.deepCopy(quoteRequestDTO, quoteRequest);
Error
It gets the following error:
Cannot construct instance of com.mycompany.transit._2008a.Service
(no Creators, like default constructor, exist): abstract types either
need to be mapped to concrete types, have custom deserializer, or
contain additional type information at [Source: UNKNOWN; byte offset:
#UNKNOWN] (through reference chain: com.mycompany.transit._2008a.availability.QuoteRequest["travel"]->com.mycompany.transit._2008a.TravelRequirement["required"]->com.mycompany.transit._2008a.Itinerary["service"]->java.util.ArrayList[0])
When I change the ServiceDTO to be an abstract class:
public abstract class ServiceDTO extends PayableDTO implements Serializable {
private static final long serialVersionUID = 1L;
It get the following error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of com.mycompany.restosgi.dto.transit.ServiceDTO
(no Creators, like default constructor, exist): abstract types either
need to be mapped to concrete types, have custom deserializer, or
contain additional type information at [Source:
(org.springframework.util.StreamUtils$NonClosingInputStream); line:
13, column: 13] (through reference chain:
com.mycompany.restosgi.dto.transit.availability.QuoteRequestDTO["travel"]->com.mycompany.restosgi.dto.transit.TravelRequirementDTO["required"]->com.mycompany.restosgi.dto.transit.ItineraryDTO["service"]->java.util.ArrayList[0])
Question
Is there a way I can write a generic utility method to deep copy objects to another object that has abstract objects?
Possible solution
Is there a way to add a converter that creates the relevant concrete class (implementation)?
The Service needs to be an implementation, for example a TransitService.
e.g.
public class TransitService extends Service implements Serializable {
private static final long serialVersionUID = 1L;
Possible Solution
As per the advise from Delta George below, I am trying the following:
public class ItineraryDTO extends PayableDTO {
protected List<ServiceDTO> service;
#JsonAnySetter
public void serService(String key, ArrayNode array) {
service = new ArrayList<>();
array.forEach(json -> service.add(toService(json)));
}
private ServiceDTO toService(JsonNode json) {
if (json.has("some unique property of flight")) {
return new ObjectMapper().convertValue(json, FlightDTO.class);
} else if (json.has("some unique property of transit")) {
return new ObjectMapper().convertValue(json, TransitServiceDTO.class);
} else return null;
}
...
However, I cannot get it to invoke the serService method. I think I need to add some Spring config to do so?
Also, if I make the ServiceDTO abstract, it get the following error.
public abstract class ServiceDTO extends PayableDTO {
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of com.clubtravel.restosgi.dto.transit.ServiceDTO
(no Creators, like default constructor, exist): abstract types either
need to be mapped to concrete types, have custom deserializer, or
contain additional type information at [Source:
(org.springframework.util.StreamUtils$NonClosingInputStream); line:
13, column: 13] (through reference chain:
com.clubtravel.restosgi.dto.transit.availability.QuoteRequestDTO["travel"]->com.clubtravel.restosgi.dto.transit.TravelRequirementDTO["required"]->com.clubtravel.restosgi.dto.transit.ItineraryDTO["service"]->java.util.ArrayList[0])
at
com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
~[jackson-databind-2.13.2.1.jar:2.13.2.1]
A concept implementation using Jackson's #JsonAnySetter to intercept incoming objects as an array of JSON trees and convert each to a concrete POJO based on the structure of each object:
#Data
#NoArgsConstructor
static abstract class Person {
}
#Data
#NoArgsConstructor
static class PersonA extends Person {
String a;
}
#Data
#NoArgsConstructor
static class PersonB extends Person {
String b;
}
#NoArgsConstructor
#ToString
static class People {
List<Person> team;
#JsonAnySetter
public void setTeam(String key, ArrayNode array) {
team = new ArrayList<>();
array.forEach(json -> team.add(toPerson(json)));
}
private Person toPerson(JsonNode json) {
if (json.has("a")) {
return new ObjectMapper().convertValue(json, PersonA.class);
} else if (json.has("b")) {
return new ObjectMapper().convertValue(json, PersonB.class);
} else return null;
}
}
public static void main(String[] args) throws JsonProcessingException {
String json = "{\"team\": [{\"a\": 123}, {\"b\": 45}]}";
People people = new ObjectMapper().readValue(json, People.class);
System.out.println(people);
// Prints: People(team=[PersonA(a=123), PersonB(b=45)])
}
// back to the OP's data model
public static class QuoteRequest {
protected TravelRequirement travel;
public TravelRequirement getTravel() {
return travel;
}
public void setTravel(TravelRequirement travel) {
this.travel = travel;
}
}
public static class TravelRequirement implements Serializable {
private static final long serialVersionUID = 1L;
protected Itinerary required;
public Itinerary getRequired() {
return required;
}
public void setRequired(Itinerary required) {
this.required = required;
}
}
public static class Itinerary extends Payable implements Serializable {
private static final long serialVersionUID = 1L;
protected List<Service> service;
public void service(List<Service> service) {
this.service = service;
}
#JsonAnyGetter
public Map<String, Object> getService() {
return Map.of("service", service);
}
#JsonAnySetter
public void setService(String key, ArrayNode array) {
service = new ArrayList<>();
array.forEach(json -> service.add(toService(json)));
}
private Service toService(JsonNode json) {
return getJacksonObjectMapper().convertValue(json, TransitService.class);
}
}
public static abstract class Service extends Payable implements Serializable {
private static final long serialVersionUID = 1L;
}
public static class TransitService extends Service implements Serializable {
private static final long serialVersionUID = 1L;
}
public static class Payable implements Serializable {
private static final long serialVersionUID = 1L;
}
private static ObjectMapper getJacksonObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
return objectMapper;
}
public static void main(String[] args) throws JsonProcessingException {
QuoteRequest qr = new QuoteRequest();
TravelRequirement tr = new TravelRequirement();
Itinerary i = new Itinerary();
i.service(List.of(new TransitService()));
tr.setRequired(i);
qr.setTravel(tr);
ObjectMapper mapper = getJacksonObjectMapper();
QuoteRequest qr2 = mapper.convertValue(qr, QuoteRequest.class);
System.out.println(qr2);
}

Get access to enclosing object in Jackson deserializer

(Jackson 2.9.9)
I have two classes:
public final class Parent {
private final String parentName;
private final Child child;
/*serialization constructor and stuff*/
}
public final class Child {
private final String someField;
/*serialization constructor and stuff*/
}
and a custom deserializer for Child registered:
public final class ChildCustomDeserializer extends JsonDeserializer<Child> {
private final List<String> parentNames;
public #NotNull T deserialize(#NotNull JsonParser parser, #NotNull DeserializationContext ctxt) {
/*deserialization logic here*/
//any way to refer Parent from here to fill parent names?
}
}
Is there any way I can get any of the parent info inside deserialize method?

Usage of abstract class in the builder pattern?

I have two types of payload coming from upstream: It's either PayloadA or PayloadB. There are some common fields between PayloadA and PayloadB so I created Payload class with those common fields and for rest I created two builder class one for each payload.
Below is the builder class for PayloadA:
public final class PayloadA {
private final String clientId;
private final String langid;
private final String deviceId;
private final Map<String, String> applicationPayload;
private PayloadA(Builder builder) {
this.clientId = builder.clientId;
this.langid = builder.langid;
this.deviceId = builder.deviceId;
this.applicationPayload = builder.applicationPayload.build();
}
public static class Builder {
protected final String deviceId;
protected String clientId;
protected String langid;
protected ImmutableMap.Builder<String, String> applicationPayload = ImmutableMap.builder();
public Builder(String deviceId) {
this.deviceId = deviceId;
}
public Builder setClientId(String clientId) {
this.clientId = clientId;
return this;
}
public Builder setLangid(String langid) {
this.langid = langid;
return this;
}
public Builder setPayload(Map<String, String> payload) {
this.applicationPayload.putAll(payload);
return this;
}
public PayloadA build() {
return new PayloadA(this);
}
}
// getters and to string here
}
Now below is the class for PayloadB:
public final class PayloadB {
private final String clientid;
private final String type;
private final String payId;
private PayloadB(Builder builder) {
this.clientid = builder.clientid;
this.type = builder.type;
this.payId = builder.payId;
}
public static class Builder {
protected final String type;
protected String payId;
protected String clientid;
public Builder(String type) {
this.type = type;
}
public Builder setPayId(String payId) {
this.payId = payId;
return this;
}
public Builder setClientId(String clientid) {
this.clientid = clientid;
return this;
}
public PayloadB build() {
return new PayloadB(this);
}
}
// getters and to string here
}
Now I have created another class which is Payload class (does this have to be abstract class?) in which I have all the common fields both for PayloadA and PayloadB so I have to set these fields as well somehow and I am not sure how to use below class:
public abstract class Payload {
private long createTimestamp;
private String partition;
private String key;
// some other fields here
// getters and setters here
}
Question:
Now let's say if we get PayloadB from upstream, then I want key field in the Payload class to be whatever is the value of type in PayloadB class in all lower case and if we get PayloadA from upstream, then I want key to be world.
And also if we get PayloadB from upstream and if clientId was set, then I want partition to be 15 and if we get PayloadA from upstream and if clientId was set then I want partition to be 15 but if it was not set and langId was there, then I want partition to be 17.
And I want to set createTimestamp as well which I have to do after building a Payload object. So for example I have build PayloadA object and it will be passed to some other class and there I need to set createTimestamp value on PayloadA object. Not sure how to do that as well? Do I have to clone something?
How can I use Payload class in my builder pattern? I will get two different payloads and there will be few things common in them so common fields I have separated them out in an abstract class.
Should I have on big builder pattern class with everything in it or multiple builder pattern extending something?
I won't pass builder instance to the PayloadX constructor. Either pass values as individual constructor arguments or call setters.
You can define Payload.Builder which would hold common fields of PayloadA and PayloadB. This class will be an abstract class declaring an abstract build method.
PayloadA.Builder and PayloadB.Builder will extend Payload.Builder, implementing the build method.
In this build method you implement whatever custom logic you need to create and set the fields of the PayloadX.
It seems like you want to make your class immutable (careful with applicationPayload by the way). In this case you can't really "set" anything. You can only produce a new instance. There are many ways to do this, for instance you can implement PayloadX withTimestamp(...) method. Or you can extend your build to accept PayloadX and set timestamp there, resulting in something like new PayloadX.Builder(payloadXInstance).setTimestamp(...).build().

Getting error while deserializing an json Object

I am having a json structure of
{"data":[{"name":"value"}]}
and my pojo class for this json is
public class RequestParamsDTO implements Serializable {
private static final long serialVersionUID = -2440599796986757919L;
private Object name;
public Object getName() {
return name;
}
public void setName(Object fields) {
this.name = fields;
}
}
Here while deserializing this json throws error,I found that declaring Object as datatype leads to the issue,if I change it to string or Integer its worked,whether some one had this issue?
Reason for declared name as Object is
This name parameter will contain string or integer dynamically in run time,I have written an logic depands on it.

GSON deserializing generic type

I have following class which is basically response from web service.
public class WSGenericMessage<T> implements Serializable {
private static final long serialVersionUID = 1L;
private Boolean ResponseCode;
private String ResponseMessage;
private Class<T> ResponseData;
public Boolean getResponseCode() {
return ResponseCode;
}
public void setResponseCode(Boolean responseCode) {
ResponseCode = responseCode;
}
public String getResponseMessage() {
return ResponseMessage;
}
public void setResponseMessage(String responseMessage) {
ResponseMessage = responseMessage;
}
public Class<T> getResponseData() {
return ResponseData;
}
public void setResponseData(Class<T> responseData) {
ResponseData = responseData;
}
}
Is it possible to deserialize json string in following way:
Gson gson = new Gson();
Type type = new TypeToken<WSGenericMessage<String>>(){}.getType();
gson.fromJson(result, type);
I am getting:
java.lang.UnsupportedOperationException: Attempted to deserialize a java.lang.Class. Forgot to register a type adapter?
There's something like type erasure, so generic types are erased and not available in runtime.
So you can deserialize object giving class without generic classifiers, and than 'cast' to proper type.
But you problem is not the generic types deserialization, but the type that contains java.lang.Class object. This can't be serialized and deserialized.
For serialization and deserialization you should use objects without Class field, use String field with class name, or mark this field as transient and restore it afterwards.

Categories

Resources