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);
}
Related
I have ObjectMapper instance:
ObjectMapper mapper = new ObjectMapper();
In runtime want to serialize instance of class. What is the class the program doesn't known. It's object instance of parameterized type T.
How to ignore all properties (fields and getters) which marked specified annotation (javax.persistence.Id) ?
Example:
public static class PojoTest {
#Id
public String idTest;
public String id;
}
public void serialize(Object object) {
ObjectMapper objectMapper = new ObjectMapper();
// TODO ignore property mark #Id annotation
Map<Object, Object> map = objectMapper.convertValue(object, Map.class);
assertFalse(map.containsKey("idTest"));
}
public void test() {
PojoTest pojoTest = new PojoTest();
pojoTest.id = "foo";
pojoTest.idTest = "bar";
serialize(pojoTest);
}
You can implement a new com.fasterxml.jackson.databind.AnnotationIntrospector class where you can extend hasIgnoreMarker method:
static class IdIgnoreAnnotationIntrospector extends AnnotationIntrospector {
#Override
public Version version() {
return new Version(1,0,0,"Ignore #Id", "group.id", "artifact.id");
}
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return hasIdAnnotation(m);
}
boolean hasIdAnnotation(AnnotatedMember member) {
return member.getAnnotation(Id.class) != null;
}
}
Now you need to register this introspector:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(AnnotationIntrospector.pair(new JacksonAnnotationIntrospector(), new IdIgnoreAnnotationIntrospector()));
Now you can ignore all fields marked with #Id annotation.
(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?
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.
I am trying to develop an AWS Lambda function that is triggered by events from SQS.
I am using the spring-cloud-function-adapter-aws (version 1.0.0.RELEASE) and in specifically a SpringBootRequestHandler.
However, the ObjectMapper that is being used is case-sensitive and therefore failing to successful convert the Json coming from SQS.
SQS publishes the following Json and it is the Records field in particular that I'm having the problem with.
{
"Records": [
{
"body": "Hello from SQS!",
"receiptHandle": "MessageReceiptHandle",
"md5OfBody": "7b270e59b47ff90a553787216d55d91d",
"eventSourceARN": "arn:aws:sqs:eu-west-1:123456789012:MyQueue",
"eventSource": "aws:sqs",
"awsRegion": "eu-west-1",
"messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
"attributes": {
"ApproximateFirstReceiveTimestamp": "1523232000001",
"SenderId": "123456789012",
"ApproximateReceiveCount": "1",
"SentTimestamp": "1523232000000"
},
"messageAttributes": {}
}
]
}
I have tried the suggestions in this question, but to no avail. Configuring ObjectMapper in Spring
In my POJO, I've also added the below annotation but it isn't working either whilst it would outside of Lambda.
#JsonProperty("Records")
private List<SqsRecord> Records;
Any help would be much appreciated.
My Lambda handler is defined as:
public class SqsEventHandler extends SpringBootRequestHandler<SqsEvent, String> {}
The POJO defined as:
public class SqsEvent {
#JsonProperty("Records")
private List<SqsRecord> records;
#Data
public class SqsRecord {
private String body;
private String receiptHandle;
private String md5OfBody;
private String eventSourceARN;
private String eventSource;
private String awsRegion;
private String messageId;
}
}
I expect the Json from the sample message to be able to be read in by the ObjectMapper, but the field "records" is null.
I got this issue solved in a more simple manner.
Referencing https://docs.aws.amazon.com/lambda/latest/dg/java-handler-io-type-stream.html and in specific
if Lambda's serialization approach does not meet your needs, you can use the byte stream implementation
I am now using the SpringBootStreamHandler directly and I have created an ObjectMapper instance with my required configuration options in my Spring Configuration class as:
#Bean
public ObjectMapper objectMapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
we've got this problem with a lot of AWS services.
You must define a new mapper like this :
SQSMixin :
private static interface SQSEventMixin {
public static final String ATTRIBUTES = "attributes";
public static final String AWS_REGION = "awsRegion";
public static final String BODY = "body";
public static final String EVENT_SOURCE = "eventSource";
public static final String EVENT_SOURCE_ARN = "eventSourceARN";
public static final String MD5_OF_BOBY = "md5OfBody";
public static final String MD5_OF_MESSAGE_ATTRIBUTES = "md5OfMessageAttributes";
public static final String MESSAGE_ID = "messageId";
public static final String RECEIPT_HANDLE = "receiptHandle";
#JsonProperty(value = "Records")
public List<?> getRecords();
static interface MessageMixin {
#JsonProperty(ATTRIBUTES)
public String getAttributes();
#JsonProperty(ATTRIBUTES)
public void setAttributes(String attributes);
#JsonProperty(AWS_REGION)
public String getAwsRegion();
#JsonProperty(AWS_REGION)
public void setAwsRegion(String awsRegion);
#JsonProperty(BODY)
public Object getBody();
#JsonProperty(BODY)
public void setBody(Object body);
#JsonProperty(EVENT_SOURCE)
public String getEventSource();
#JsonProperty(EVENT_SOURCE)
public void setEventSource(String eventSource);
#JsonProperty(EVENT_SOURCE_ARN)
public String getEventSourceArn();
#JsonProperty(EVENT_SOURCE_ARN)
public void setEventSourceArn(String eventSourceArn);
#JsonProperty(MD5_OF_BOBY)
public String getMd5OfBody();
#JsonProperty(MD5_OF_BOBY)
public void setMd5OfBody(String md5OfBody);
#JsonProperty(MD5_OF_MESSAGE_ATTRIBUTES)
public String getMd5OfMessageAttributes();
#JsonProperty(MD5_OF_MESSAGE_ATTRIBUTES)
public void setMd5OfMessageAttributes(String md5OfMessageAttributes);
#JsonProperty(MESSAGE_ID)
public String getMessageId();
#JsonProperty(MESSAGE_ID)
public void setMessageId(String messageId);
#JsonProperty(RECEIPT_HANDLE)
public String getReceiptHandle();
#JsonProperty(RECEIPT_HANDLE)
public void setReceiptHandle(String receiptHandle);
}
}
A Strategy for record :
private static class UpperCaseRecordsPropertyNamingStrategy extends PropertyNamingStrategy.PropertyNamingStrategyBase {
private static final long serialVersionUID = 1L;
#Override
public String translate(String propertyName) {
if (propertyName.equals("records")) {
return "Records";
}
return propertyName;
}
}
Formatter for Date :
private static final DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTime()
.withZone(new FixedDateTimeZone("GMT", "GMT", 0, 0));
private static class DateTimeMapperModule extends SimpleModule {
private static final long serialVersionUID = 1L;
public DateTimeMapperModule() {
super("DateTimeMapperModule");
super.addSerializer(DateTime.class, new DateTimeSerializer());
super.addDeserializer(DateTime.class, new DateTimeDeserializer());
}
}
private static class DateTimeSerializer extends JsonSerializer<DateTime> {
#Override
public void serialize(DateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(dateTimeFormatter.print(value));
}
}
private static class DateTimeDeserializer extends JsonDeserializer<DateTime> {
#Override
public DateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException {
return dateTimeFormatter.parseDateTime(parser.getText());
}
}
And declare your mapper :
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
mapper.setPropertyNamingStrategy(new UpperCaseRecordsPropertyNamingStrategy());
mapper.registerModule(new DateTimeMapperModule());
mapper.addMixIn(SQSMessage.class, SQSEventMixin.MessageMixin.class);
SQSEvent request = mapper.convertValue(inputObject, SQSEvent.class);
There is already an official library that is supporting this: https://aws.amazon.com/blogs/opensource/testing-aws-lambda-functions-written-in-java/
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-tests</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
Also have surefire in your plugins:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
Example:
SQSEvent input = EventLoader.loadEvent("/sqsEvent.json", SQSEvent.class);
We use jackson throughout our application to serialize and deserialize Java objects to JSON. It works great.
Is it possible, perhaps through a custom serializer, to serialize only properties of a Java object that are Annotated with a custom annotation?
So, given the custom annotation:
public #interface SpecialField {}
And the following bean
public SomeBean {
#SpecialField
private Object propertyIncluded;
private Object propertyExcluded;
}
What would a custom serializer (or some equivalent mechanism) look like to serialize propertyIncluded (using the normal jackson object mapper) and ignore propertyExcluded?
We can't use standard jackson annotations (#JsonIgnore) in this use case because it would break our other serialization uses cases in the application.
While this might not be quite what your looking for, It is possible to make the jackson engine serialize objects differently via some tweaking. In my example below I create two types of serializers which will or wont serialize a field marked as transient.
import java.io.Serializable;
import org.codehaus.jackson.annotate.JsonAutoDetect;
import org.codehaus.jackson.map.ObjectMapper;
public class Test {
public static void main(String[] args) throws Exception {
ISerializer d = new Doesnt();
ISerializer o = new Observes();
SomeObject obj = new SomeObject();
System.out.println("Doesnt: " + d.serialize(obj));
System.out.println("Observes: " + o.serialize(obj));
}
public static class Doesnt implements ISerializer<SomeObject> {
#Override
public String serialize(SomeObject o) throws Exception {
ObjectMapper om = new ObjectMapper();
om.setVisibilityChecker(
om.getSerializationConfig().
getDefaultVisibilityChecker().
withFieldVisibility(JsonAutoDetect.Visibility.ANY).
withGetterVisibility(JsonAutoDetect.Visibility.ANY));
return om.writeValueAsString(o);
}
}
public static class Observes implements ISerializer<SomeObject> {
#Override
public String serialize(SomeObject o) throws Exception {
ObjectMapper om = new ObjectMapper();
om.setVisibilityChecker(
om.getSerializationConfig().
getDefaultVisibilityChecker().
withFieldVisibility(JsonAutoDetect.Visibility.ANY).
withGetterVisibility(JsonAutoDetect.Visibility.NONE));
return om.writeValueAsString(o);
}
}
public interface ISerializer<T> {
public String serialize(T o) throws Exception;
}
public static class SomeObject implements Serializable {
private static final long serialVersionUID = 745063791749142843L;
private transient String myVar = "Transient";
private String myOther = "Not Transient";
public String getMyVar() {
return myVar;
}
public void setMyVar(String myVar) {
this.myVar = myVar;
}
public String getMyOther() {
return myOther;
}
public void setMyOther(String myOther) {
this.myOther = myOther;
}
}
}
output:
Doesnt: {"myVar":"Transient","myOther":"Not Transient"}
Observes: {"myOther":"Not Transient"}
I would think it would be fairly easy to change serializers to extend the JsonSerializer class, and do something similar in them.