Jackson filtering out fields without annotations - java

I was trying to filter out certain fields from serialization via SimpleBeanPropertyFilter using the following (simplified) code:
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("test",
SimpleBeanPropertyFilter.filterOutAllExcept("data1"));
try {
String json = mapper.writer(filterProvider).writeValueAsString(new Data());
System.out.println(json); // output: {"data1":"value1","data2":"value2"}
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
private static class Data {
public String data1 = "value1";
public String data2 = "value2";
}
Us I use SimpleBeanPropertyFilter.filterOutAllExcept("data1")); I was expecting that the created serialized Json string contains only {"data1":"value1"}, however I get {"data1":"value1","data2":"value2"}.
How to create a temporary writer that respects the specified filter (the ObjectMapper can not be re-configured in my case).
Note: Because of the usage scenario in my application I can only accept answers that do not use Jackson annotations.

If for some reason MixIns does not suit you. You can try this approach:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector(){
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
List<String> exclusions = Arrays.asList("field1", "field2");
return exclusions.contains(m.getName())|| super.hasIgnoreMarker(m);
}
});

You would normally annotate your Data class to have the filter applied:
#JsonFilter("test")
class Data {
You have specified that you can't use annotations on the class. You could use mix-ins to avoid annotating Data class.
#JsonFilter("test")
class DataMixIn {}
Mixins have to be specified on an ObjectMapper and you specify you don't want to reconfigure that. In such a case, you can always copy the ObjectMapper with its configuration and then modify the configuration of the copy. That will not affect the original ObjectMapper used elsewhere in your code. E.g.
ObjectMapper myMapper = mapper.copy();
myMapper.addMixIn(Data.class, DataMixIn.class);
And then write with the new ObjectMapper
String json = myMapper.writer(filterProvider).writeValueAsString(new Data());
System.out.println(json); // output: {"data1":"value1"}

The example of excluding properties by name:
public Class User {
private String name = "abc";
private Integer age = 1;
//getters
}
#JsonFilter("dynamicFilter")
public class DynamicMixIn {
}
User user = new User();
String[] propertiesToExclude = {"name"};
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Object.class, DynamicMixIn.class);
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("dynamicFilter", SimpleBeanPropertyFilter.filterOutAllExcept(propertiesToExclude));
mapper.setFilterProvider(filterProvider);
mapper.writeValueAsString(user); // {"name":"abc"}
You can instead of DynamicMixIn create MixInByPropName
#JsonIgnoreProperties(value = {"age"})
public class MixInByPropName {
}
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Object.class, MixInByPropName.class);
mapper.writeValueAsString(user); // {"name":"abc"}
Note: If you want exclude property only for User you can change parameter Object.class of method addMixIn to User.class
Excluding properties by type you can create MixInByType
#JsonIgnoreType
public class MixInByType {
}
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Integer.class, MixInByType.class);
mapper.writeValueAsString(user); // {"name":"abc"}

It seems you have to add an annotation which indicts which filter to use when doing the serialization to the bean class if you want the filter to work:
#JsonFilter("test")
public class Data {
public String data1 = "value1";
public String data2 = "value2";
}
EDIT
The OP has just added a note that just take the answer that not using a bean animation, then if the field you want to export is very less amount, you can just retrieve that data and build a Map of List yourself, there seems no other way to do that.
Map<String, Object> map = new HashMap<String, Object>();
map.put("data1", obj.getData1());
...
// do the serilization on the map object just created.
If you want to exclude specific field and kept the most field, maybe you could do that with reflect. Following is a method I have written to transfer a bean to a map you could change the code to meet your own needs:
protected Map<String, Object> transBean2Map(Object beanObj){
if(beanObj == null){
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(beanObj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
if (!key.equals("class")
&& !key.endsWith("Entity")
&& !key.endsWith("Entities")
&& !key.endsWith("LazyInitializer")
&& !key.equals("handler")) {
Method getter = property.getReadMethod();
if(key.endsWith("List")){
Annotation[] annotations = getter.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof javax.persistence.OneToMany){
if(((javax.persistence.OneToMany)annotation).fetch().equals(FetchType.EAGER)){
List entityList = (List) getter.invoke(beanObj);
List<Map<String, Object>> dataList = new ArrayList<>();
for(Object childEntity: entityList){
dataList.add(transBean2Map(childEntity));
}
map.put(key,dataList);
}
}
}
continue;
}
Object value = getter.invoke(beanObj);
map.put(key, value);
}
}
} catch (Exception e) {
Logger.getAnonymousLogger().log(Level.SEVERE,"transBean2Map Error " + e);
}
return map;
}
But I recommend you to use Google Gson as the JSON deserializer/serializer And the main reason is I hate dealing with exception stuff, it just messed up with the coding style.
And it's pretty easy to satisfy your need with taking advantage of the version control annotation on the bean class like this:
#Since(GifMiaoMacro.GSON_SENSITIVE) //mark the field as sensitive data and will not export to JSON
private boolean firstFrameStored; // won't export this field to JSON.
You can define the Macro whether to export or hide the field like this:
public static final double GSON_SENSITIVE = 2.0f;
public static final double GSON_INSENSITIVE = 1.0f;
By default, Gson will export all field that not annotated by #Since So you don't have to do anything if you do not care about the field and it just exports the field.
And if some field you are not want to export to json, ie sensitive info just add an annotation to the field. And generate json string with this:
private static Gson gsonInsensitive = new GsonBuilder()
.registerTypeAdapter(ObjectId.class,new ObjectIdSerializer()) // you can omit this line and the following line if you are not using mongodb
.registerTypeAdapter(ObjectId.class, new ObjectIdDeserializer()) //you can omit this
.setVersion(GifMiaoMacro.GSON_INSENSITIVE)
.disableHtmlEscaping()
.create();
public static String toInsensitiveJson(Object o){
return gsonInsensitive.toJson(o);
}
Then just use this:
String jsonStr = StringUtils.toInsensitiveJson(yourObj);
Since Gson is stateless, it's fine to use a static method to do your job, I have tried a lot of JSON serialize/deserialize framework with Java, but found Gson to be the sharp one both performance and handily.

Related

Prevent Object Mapper readerForUpdating overwriting existing data

I am trying to update value of one object from another using object mapper readerForUpdating function. But I have a use case where I have to update only null values of my existing object which I am unable to do so. Could anyone please help on this.
Class:
#Getter
#Setter
class Record {
private String source;
private String resource;
}
Record Object:
Record record = new Record();
record.setSource("SourceFromObject");
record.setResource(null);
Object to be Updated:
String incomingJson = "{"source":"SourceFromJson", "resource":"ResourceFromJson"}"
ObjectMapper Logic:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.readerForUpdating(record).readValue(incomingJson);
Am getting the below output
Record: {"source":"SourceFromJson", "resource":"ResourceFromJson"}
But my expected output is
Record: {"source":"SourceFromObject", "resource":"ResourceFromJson"}
I tried several suggestions but didn't worked. I have also tried #JsonMerge annotation.
That's the expected behavior for the ObjectReader returned by ObjectMapper.readerForUpdating(), it would update the given Object with the provided JSON data.
Therefore, you see "SourceFromJson" as the value of the source.
Jackson Databinding
If you want to preserve the value of the field source intact. Then you can annotate it with #JsonIgnore.
Or you can apply #JsonProperty annotation on the source field specifying the access attribute with a value of JsonProperty.Access.READ_ONLY (that would prevent Jackson from writing into this field, but it would be reflected during serialization of the POJO).
Smart Setters
Another option would be to add validation logic into the setter method, to ensure that each gets updated only if it's null (empty, etc. depending on the type and your requirements).
Here's how it might look like:
#Getter
public static class Record {
public static final Predicate<String> NULL_OR_EMPTY =
s -> s == null || s.isEmpty(); // predicate can be reused to validate multiple properties
private String source;
private String resource;
public void setSource(String source) {
if (NULL_OR_EMPTY.test(this.source)) this.source = source;
}
public void setResource(String resource) {
if (NULL_OR_EMPTY.test(this.resource)) this.resource = resource;
}
}
Usage example:
public static void main(String[] args) throws IOException {
String incomingJson = """
{"source":"SourceFromJson", "resource":"ResourceFromJson"}
""";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.readerForUpdating(record).readValue(incomingJson);
System.out.println(record);
String str = "helloslkhellodjladfjhello";
System.out.println(countOccurrences(str, "hello"));
}
Output:
Record{source='SourceFromObject', resource='ResourceFromJson'}

Jackson Custom Serializer shows the same context for 2 different field during the Json Serialization

I am trying to create a JSON based on my Object class POJO. For some fields, I would like to use the CustomSerializer as I would like to create the fields according to my requirement. Hence, I have created the CustomSerializer.class.
The CustomSerializer will be called by 2 different fields in my POJO and I would like to handle the things differently based on which field is making the call. For one of the fields (extensions) I would like to have the fieldName and for other field (withoutExtensions) I do not wish to have the fieldname in my JSON.
The problem I am facing is that when CustomSerializer is called then I am getting the same fieldname for both the calls due to which I am unable to make a differentiation which field is currently calling the CustomSerializer.
Following code samples will provide more clarity on the issue I am facing:
Customer POJO class used for serializing the JSON:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
#NoArgsConstructor
public class Customer {
private String isA;
private String name;
#JsonSerialize(using = CustomSerializer.class)
private Map<String, Object> extensions = new HashMap<>();
private Map<String, Object> withoutExtensions = new HashMap<>();
#JsonAnyGetter
#JsonSerialize(using = CustomSerializer.class)
public Map<String, Object> getWithoutExtensions() {
return withoutExtensions;
}
}
Following is my CustomSerializer which will be called by 2 fields (extensions and withoutExtensions) during the creation of JSON:
public class CustomSerializer extends JsonSerializer<Map<String, Object>> {
#Override
public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) {
//I would like to create the outer object for "Extensions" but do not want to create outer object for "WithoutExtensions"
System.out.println(gen.getOutputContext().getCurrentName());
//In my case for both "Extensions" and "WithoutExtensions" i get the "currentName" as "Extensions" how can I ensure which field is calling this sealizer at
// present
}
}
Following is my Main class which will create a JSON:
public class Main {
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
final Customer customer = new Customer();
customer.setName("Jackson");
Map<String, Object> extensions = new HashMap<>();
extensions.put("WithObject", "With");
customer.setExtensions(extensions);
Map<String, Object> withoutExtensions = new HashMap<>();
extensions.put("WithoutObject", "Without");
customer.setWithoutExtensions(withoutExtensions);
final String eventAsJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
System.out.println(eventAsJson);
}
}
As we can see when I run the application the CustomSerializer would print extensions in both cases. I believe it should print extensions only once and in the next case either it should provide withoutExtensions or empty string.
I just wanted to know if this an bug on the Jackson part or is there any work-around that I can try to differentiate which field is making a call to my CustomSerializer.
Any help would be really appreciated. Thanks.
A. Create two Map serialisers where one creates outer object and another not
Pros:
Easy to implement
Easy to test
One class does exactly one thing
Map serialiser which does not create outer object could be replaced by custom Map serialiser (if possible)
Cons:
Could be problematic if they need to share state.
Possibly duplicated code
B. Implement ContextualSerializer interface
Pros:
Can be configured for every field separately
Can share state if needed. User control how many instances are created.
Cons:
Does more than 1 thing
Can be easily over complicated
Examples:
Need Jackson serializer for Double and need to specify precision at runtime
Jackson custom annotation for custom value serialization
Deserialize to String or Object using Jackson
Jackson - deserialize inner list of objects to list of one higher level
Based on the response from #Michal I modified the code and it worked for both the scenario. Posting the complete code sample as it can be helpful to someone in the future:
Customer.class added the #Extensions on required fields:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
#NoArgsConstructor
public class Customer {
private String isA;
private String name;
#JsonSerialize(using = CustomSerializer.class)
#Extensions(extension = "extensions")
private Map<String, Object> extensions = new HashMap<>();
private Map<String, Object> withoutExtensions = new HashMap<>();
#JsonAnyGetter
#JsonSerialize(using = CustomSerializer.class)
#Extensions(extension = "withoutExtensions")
public Map<String, Object> getWithoutExtensions() {
return withoutExtensions;
}
}
CustomSerializer:
#NoArgsConstructor
public class CustomSerializer extends JsonSerializer<Map<String, Object>> implements ContextualSerializer {
private String context = "";
public CustomSerializer(String context) {
this.context = context;
}
#Override
public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) {
if (this.context.equals("extensions")) {
System.out.println("Extensions : " + this.context);
} else if (this.context.equals("withoutExtensions")) {
System.out.println("Without Extensions : " + this.context);
}
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
Extensions extensions = beanProperty.getAnnotation(Extensions.class);
if (extensions != null) {
return new CustomSerializer(extensions.extension());
}
return this;
}
}
#Target({ElementType.FIELD, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#interface Extensions {
String extension();
}

How would I print an object's name as part of the JSON in Java's Jackson library?

I'm trying to determine how to print out a JSON that looks like this, using Java's Jackson library:
{
"status": {
{
"busStatus" : {
"status" : null,
"transactions" : "0",
"retries" : "0",
"failures" : "0"
}
}
}
}
I'm 95% there, but the outermost object is not currently being printed. This is what I'm currently getting outputted:
{
"busStatus" : {
"status" : null,
"transactions" : "0",
"retries" : "0",
"failures" : "0"
}
}
I have a Java class that looks like this:
public class DataClass {
public StatusData status = new StatusData();
public StatusConfig config = new StatusConfig();
public class StatusData {
public SerialStatus busStatus = new SerialStatus();
}
public class StatusConfig {
}
public class SerialStatus {
public String status = null;
public String transactions = "0";
public String retries = "0";
public String failures = "0";
}
}
I'm printing this class to json using the code below:
private DataClass internalData;
ObjectMapper mapper = new ObjectMapper();
status = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(internalData.status);
Is there a way I can configure Jackson to print out the name of the object its serializing into the JSON?
To achieve what you want, you need to print DataClass instead of StatusData. Something like below:
private DataClass internalData = <initialize>;
ObjectMapper mapper = new ObjectMapper();
String data =
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(internalData);
You can use Jackson Filter to control the serialization process, I think it should work with your use case, at least one way to do it.
Use the filter annotation and then create two different filters for your class, where you can define which field to skip, and use it with the ObjectMapper accordingly to convert the whole internalData object, so when you need to skip the status, use one filter and when you need to skip the config associate the other filter with the mapper, while always serializing the parent object. Which should give you the structure you want.
#JsonFilter("filter_serializer")
class User {
public String v1;
public String v2;
}
String[] fieldsToSkip = new String[] { "v1" };
ObjectMapper mapper = new ObjectMapper();
final SimpleFilterProvider filter = new SimpleFilterProvider();
filter.addFilter("filter_serializer",
SimpleBeanPropertyFilter.serializeAllExcept(fieldsToSkip));
User dtoObject = new User();
dtoObject.v1 = "v1";
dtoObject.v2 = "v2";
String jsonStr = mapper.writer(filter).writeValueAsString(dtoObject);
I was able to find the solution I was looking for, from this website.
I've gotten rid of the DataClass and now only have a StatusData and a StatusConfig class. I've included how the StatusData class would look below:
#JsonRootName(value = "status")
public class StatusData {
String status;
String transactions;
// so on
}
To parse the class, I needed to add the JsonRootName annotation above, and also enable a feature on the mapper, as below:
private DataClass internalData;
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE); // don't forget this!
statusText = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(statusObject);
Separately, if you'd like to deserialize a JSON like the one I had into a class like StatusData, do this:
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
statusObject = mapper.readValue(statusText, StatusData.class);

How java jackson deserializer handle both Boolean and Object on same field

I'm working with a 3rd party JSON API, it returns data like this:
{details: {...}, ...}
I use Java Jackson to deserialize this JSON string into a POJO object, the field declaration is :
#JsonProperty("details")
public Details getDetails(){...}
and Details is another class.
Everything is fine until I found that API may return data like this:
{details: false, ...}
If details is empty, it returns false!!! And jackson gave me this exception:
com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class Details] from Boolean value; no single-boolean/Boolean-arg constructor/factory method (through reference chain: ...["details"])
So, how to handle this kind of JSON string? I only need this field to set to null if empty.
The error message from Jackson hints that the library has bulit in support for static factory methods. This is (perhaps) a simpler solution than a custom deserializer:
I created this example POJO, with a static factory method, annotated so that Jackson uses it:
public class Details {
public String name; // example property
#JsonCreator
public static Details factory(Map<String,Object> props) {
if (props.get("details") instanceof Boolean) return null;
Details details = new Details();
Map<String,Object> detailsProps = (Map<String,Object>)props.get("details");
details.name = (String)detailsProps.get("name");
return details;
}
}
test method:
public static void main(String[] args)
{
String fullDetailsJson = "{\"details\": {\"name\":\"My Details\"}} ";
String emptyDetailsJson = "{\"details\": false} ";
ObjectMapper mapper = new ObjectMapper();
try {
Details details = mapper.readValue(fullDetailsJson, Details.class);
System.out.println(details.name);
details = mapper.readValue(emptyDetailsJson, Details.class);
System.out.println(details);
} catch (Exception e) {
e.printStackTrace();
}
}
result is as expected:
My Details
null
Make a custom JsonDeserializer to handle deserializing your Details object in which you either return null if you get false or pass the object to the default deserializer if it's an actual object. Pseudocode:
public class CustomDeserializer extends JsonDeserializer<Details>{
#Override
public Details deserialize(JsonParser jsonParser, DeserializationContext ctx){
//if object use default deserializer else return null
}
}
You'll also have to write an ObjectMapperProvider to register your deserializer like so:
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper>{
private ObjectMapper mapper;
public ObjectMapperProvider(){
mapper = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addDeserializer(Details.class, new CustomDeserializer());
mapper.registerModule(sm);
}
public ObjectMapper getContext(Class<?> arg0){
return mapper;
}
}

How to save arbitrarily-structured documents with Java DynamoDBMapper class

I'm using Amazon's DynamoDBMapper Java class to save data to a DynamoDB table. This code needs to work for data structured in multiple different ways, so I would like to stay away from writing particularly structure-specific code. For this reason, I store the code as JSON objects in Java -- which are basically glorified HashMaps.
I would like to store these JSON objects into DynamoDB as Dynamo's relatively new JSON Document type.
The way the DynamoDBMapper API works is essentially that you write a Java class (typically a POJO), then add some annotations, then pass your objects of that class into DynamoDBMapper so that it can then put items into the database with the structure of the Java class. This works well for many aspects of what I'm doing, but not with the fact that I want these classes to contain arbitrarily-structured JSON documents. This is the way you're meant to store JSON documents using DynamoDBMapper, and as you can see, it doesn't allow for the structure of the documents to be arbitrary.
I realize I could use Dynamo's putItem() to pass the jsons as Strings into Item objects -- I just wanted to see if what I want to do is possible with DynamoDBMapper before I shift my approach.
You can try using the DynamoDB Java document SDK instead of the object mapper. This allows you to serialize and deserialize JSON strings using the fromJSON and toJSON methods in the Item class. Check out http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/JavaDocumentAPIItemCRUD.html.
Here's how I came up with my answer of how to store arbitrary Map objects in DynamoDB. This is extremely useful for archiving REST API responses that have been unmarshaled to foreign objects. I'm personally using this to archive REST responses from the PayPal Payment API. I don't care what variables they use in their REST API or the structure of their POJO / beans. I just want to make sure I save everything.
#DynamoDBTable(tableName = "PaymentResponse")
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes({
#JsonSubTypes.Type(value = PayPalPaymentResponse.class, name = "PayPalPaymentResponse"),
#JsonSubTypes.Type(value = BatchPayPalPaymentResponse.class, name = "BatchPayPalPaymentResponse")}
)
public abstract class PaymentResponse {
// store any arbitrary REST resrponse data in map form so we don't have to worry about the
// structure or the actual response itself
protected Map<String, String> paymentResponseData = Maps.newHashMap();
public PaymentResponse(PaymentResponseType paymentResponseType) {
this.paymentResponseType = paymentResponseType;
}
public Map<String, String> getPaymentResponseData() { return paymentResponseData; }
public void setPaymentResponseData(Map<String, String> paymentResponseData) { this.paymentResponseData = paymentResponseData; }
#Override
public String toString() {
return Arrays.toString(paymentResponseData.entrySet().toArray());
}
}
public class ConverterUtils {
public static BatchPayPalPaymentResponse getBatchPayPalPaymentResponse(PayoutBatch payoutBatch) throws IOException {
//read in the PayoutBatch response data and convert it first to a JSON string and then convert the
//JSON string into a Map<String, String>
Map<String, String> responseData = objectMapper.readValue(objectMapper.writeValueAsString(payoutBatch), new TypeReference<Map<String, String>>() {});
BatchPayPalPaymentResponse batchPayPalPaymentResponse = new BatchPayPalPaymentResponse(responseData);
return batchPayPalPaymentResponse;
}
public static PayPalPaymentResponse getSinglePayPalPaymentResponse(PayoutItemDetails payoutItemDetails) throws IOException {
//read in the paypal PayoutItemDetails response data and convert it first to a JSON string and then convert the
//JSON string into a Map<String, String>
Map<String, String> responseData = objectMapper.readValue(objectMapper.writeValueAsString(payoutItemDetails), new TypeReference<Map<String, String>>() {});
PayPalPaymentResponse payPalPaymentResponse = new PayPalPaymentResponse(responseData);
return payPalPaymentResponse;
}
}
public class BatchPayPalPaymentResponse extends PaymentResponse {
public BatchPayPalPaymentResponse(Map<String, String> responseData) {
super(responseData);
}
....
....
....
}
public class PayPalPaymentResponse extends PaymentResponse {
public PayPalPaymentResponse(Map<String, String> responseData) {
super(responseData);
}
....
....
....
}
Now you can just call mapper.save(instanceOfPaymentResponse). Note that my code also includes how to use a Jackson parser to pick and choose which sub-class of PaymentResponse to unmarshal too. That's because I use a DynamoDBTypeConverter to marshal my class to a string before putting it into the database.
Finally, I'll throw in my converter for completeness so it all hopefully makes sense.
public class PaymentResponseConverter implements DynamoDBTypeConverter<String, PaymentResponse> {
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
#Override
public String convert(PaymentResponse object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(String.format("Received invalid instance of PaymentResponse and cannot marshal it to a string (%s)", e.getMessage()));
}
}
#Override
public PaymentResponse unconvert(String object) {
try {
return objectMapper.readValue(object, PaymentResponse.class);
} catch (IOException e) {
throw new IllegalArgumentException(String.format("Unable to convert JSON to instance of PaymentResponse. This is a fatal error. (%s)", e.getMessage()));
}
}
}
I had the same problem and went the route of serializing and deserializing objects to json string by myself and then just store them as strings. The whole Document concept of DynamoDB is IMHO just a glorified object serializer. Only if you need to access attributes inside your object in dynamodb actions (eg. scans, projections) it makes sense to use the json document type. If our data is opaque to dynamodb, you are better off with strings.

Categories

Resources