Get access to enclosing object in Jackson deserializer - java

(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?

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);
}

Polymorphic deserialization JSON by using POJOS

I want to consume a json with jax-rs my method stamp look like that.
#PostMapping("/test")
public ResponseEntity<String> consumeJson(#RequestBody TestPojo testPojo)
My json look like that
{
"code": "<code>",
"display": "<display>",
"activities": [
{
"categoryCode": "drug",
"drugDisplay" : "Ceforanide"
},{
"categoryCode": "observation",
"measurementWeight" : "80kg",
}
]
}
And i have the following pojos
public class TestPojo implements Serializable{
private String code;
private String display;
private List<ActivityPojo> activities;
// Getters & Setters
}
Now i have a super class and couple of classes inherit from it
public class ActivityPojo implements Serializable{
private String categoryCode;
}
The child classes
public class DrugPojo extends ActivityPojo implements Serializable{
private String drugDisplay;
// Getters & Setters
}
public class ObservationPojo extends ActivityPojo implements Serializable{
private String measurementWeight;
// Getters & Setters
}
Inside my webservice method i want to do something like that
List<ActivityPojo> activities = testPojo.getActivities();
for(int i = 0; i < activities.size(); i++){
if( activities.get(i) instanceof DrugPojo){
// do stuff
}
else if( activities.get(i) instanceof ObservationPojo){
// do stuff
}
}
So can polymorphically serialize my json in order to do that. Any help would be appreciated.
This question is very interresting so I did a few tests.
If I understood correctly the problem, I think this class (and the inner one) can solve it :
#Component
#SuppressWarnings("serial")
public class ActivityPojoJsonModule extends SimpleModule {
public ActivityPojoJsonModule() {
this.addDeserializer(ActivityPojo.class, new ActivityPojoDeserializer());
}
public static class ActivityPojoDeserializer extends JsonDeserializer<ActivityPojo> {
#Override
public ActivityPojo deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
ObjectCodec codec = parser.getCodec();
JsonNode node = codec.readTree(parser);
if(this.isDrug(node)) {
return codec.treeToValue(node, DrugPojo.class);
}
return codec.treeToValue(node, ObservationPojo.class);
}
private boolean isDrug(JsonNode node) {
return node.get("categoryCode").asText().equals("drug");
}
}
}
It adds a component to the Spring context that will deserialize ActivityPojo with a logic based on the value of the field categoryCode. You just have to add this class in the a scanned package and it will override the default behaviour of Jackson.

The method returns a superclass object with subclass fields

I have a Movie object in the program
public class Movie extends BaseDTO {
...
public static class Builder<T extends Builder> extends BaseDTO.Builder<T> { ... }
}
from this object inherits UserMovie
public class UserMovie extends Movie {
//there are two additional fields
private final Integer yourRating;
private final boolean favorited;
public static class Builder extends Movie.Builder<Builder> { ... }
}
In some part of the code I want to convert entities to DTO objects. I have two methods. One to convert entities into Movie DTO
static Movie toMovieDto(final MovieEntity movieEntity) {
return ((Movie.Builder) initMovieDto(movieEntity)).build();
}
the second method to convert to UserMovie DTO
static UserMovie toUserMovieDto(final MovieEntity movieEntity, final UserEntity userEntity) {
final UserMovie.Builder builder = initMovieDto(movieEntity);
builder.withYourRating(...);
builder.withFavorited(userEntity.getFavoritesMovies().contains(movieEntity));
return builder.build();
}
both methods use a common method to initialize data in the Builder pattern
private static UserMovie.Builder initMovieDto(final MovieEntity movieEntity) {
final UserMovie.Builder builder = new UserMovie.Builder(
movieEntity.getTitle(),
movieEntity.getType()
)
.withId(movieEntity.getId().toString());
builder.withRating(...);
...
return builder;
}
It turns out that the toMovieDto method returns the UserMovie object. Why is this happening? I do not want private fields for UserMovie to be returned in Movie. How to fix it?
Are you worried that someone might cast the Movie returned from toMovieDto into UserMovie? I wouldn't worry about that much - this requires explicit casting. (Compare it how methods in e.g. Collections class are defined, or in any Collection implementations).
If you are really worried about security (and only in this case) you need to create another initMovieDto method, that user builder from Movie and not from UserMovie like currently.
So something like this:
private static Movie.Builder initMovieDto(final MovieEntity movieEntity) {
final Movie.Builder builder = new Movie.Builder(
movieEntity.getTitle(),
movieEntity.getType()
)
.withId(movieEntity.getId().toString());
...
return builder;
}
static Movie toMovieDto(final MovieEntity movieEntity) {
return ((Movie.Builder) initMovieDto(movieEntity)).build();
}
and separate for UserMovie:
private static UserMovie.Builder initUserMovieDto(final MovieEntity movieEntity) {
final UserMovie.Builder builder = new UserMovie.Builder(
movieEntity.getTitle(),
movieEntity.getType()
)
.withId(movieEntity.getId().toString());
builder.withRating(...);
...
return builder;
}
static UserMovie toUserMovieDto(final MovieEntity movieEntity, final UserEntity userEntity) {
final UserMovie.Builder builder = initUserMovieDto(movieEntity);
builder.withYourRating(...);
builder.withFavorited(userEntity.getFavoritesMovies().contains(movieEntity));
return builder.build();
}
This way you will return not only the interface like you want but also concrete class of the same type as the return type in toMovieDto and toUserMovieDto.
Another option would be to define UserMovie builder such that one of it's options would accept a Movie builder.

Serializing an object containing an interface

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?

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().

Categories

Resources