I've a model with field annotated as a:
#JsonTypeInfo(use = CLASS)
private Object dudClass;
The main problem is when I try to deserialize this object and I don't have this dudClass instance on classpath I will receive an exception:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Invalid type id 'pl.erbel.DudClass' (for id type 'Id.class'): no such class found.
Is it any easy way to just ignore this exception? I don't want to ignore serialization/deserialization cause I need this in different module. I just have a two clients: one with DudClass on classpath and the seconde one without that class.
It might not be the perfect solution but this is a work around. Instead of having dudClass in your POJO you can make use of the additional properties using JsonAnyGetter and JsonAnySetter. You need to remove the dudClass from your parent class and include the following code
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
What this does is while deserializing instead of trying to set the dudClass everytime it sets any additional objects that comes along in the response to the Map with object name as the key (in your case it's dudClass) and the object contents as the value. You just have see if the Map has dudClass key present or not.
Let me know if this doesn't answer your issue or need clarification!
Related
I am trying to deserialize an Object from a Map(String, Object) via FasterJackson's ObjectMapper.convertValue(map, ComplexRecord.class) method.
My destination object contains many other objects within it. The only problematic object is the embedded "AnyObject" object that uses FasterJackson's (Jackson's) AnyGetter/AnySetter methods.
The AnyObject instance works for every other use case with FasterJackson except, at the moment, for this one where it involves a more "ComplexRecord" deserialization.
This is what it looks like:
#Data
public class ComplexRecord {
private String id;
private AnyObject data;
private String status;
private Instant createdDateTime;
}
#Data
public class AnyObject {
#Getter(AccessLevel.NONE)
#Setter(AccessLevel.NONE)
private final Map<String, Object> data;
public AnyObject() {
this(new LinkedHashMap<>());
}
public AnyObject(Map<String, Object> sourceData) {
this.data = new LinkedHashMap<>(sourceData);
}
#JsonAnySetter
public void setData(String name, Object value) {
this.data.put(name, value);
}
#JsonAnyGetter
public Map<String, Object> getData() {
return Collections.unmodifiableMap(this.data);
}
}
When I try to use ObjectMapper.convertValue(map, ComplexRecord.class) it fails to deserialize the "data" field due to this error:
Cannot construct instance of `AnyObject`
(although at least one Creator exists): no String-argument
constructor/factory method to deserialize from String value
('{"id":"123","v":"anything"}')
at [Source: UNKNOWN; line: -1, column: -1]
I'd like to find a very clean workaround for this. This issue seems to stem from the fact that I am using the ObjectMapper.convertValue method from a complex source of Map where the key "data" is originally available as a String. If I perform a similar operation but with ObjectMapper.readValue() instead of ObjectMapper.convertValue() then the deserialization into AnyObject works just fine.
Since I do not have the luxury of changing the source object of Map into something that would work with ObjectMapper.readValue() method I may be left with only a few choices.
One option I found is using FasterJackson's Custom Deserializer. The only catch I see is that there is no clear way to access the internal ObjectMapper that is provided to the Deserializer. When I debug the deserializer, I do see that JsonParser.getCodec() is the ObjectMapper, but even when trying to do a readValue within the the custom Deserializer the deserialization fails with the same error. i.e.
AnyObject value = jsonParser.getCodec().readValue(p, AnyObject.class);
However following set of calls work just fine:
String stringValue = jsonParser.getCodec().readValue(p, String.class);
AnyObject anyObject = objMapper.readValue(stringValue, AnyObject.class);
Other than this being a 2 step process to deserialize, rather than a 1 step process; the only other issue is that I do not have a clean way to use the "objMapper" (ObjectMapper) instance I am referring to above without casting the codec into an ObjectMapper instance.
This seems like a hack to me but I'd like to get your thoughts and also see if there any other friendlier solution available.
There are a few more thoughts / options but I'd like to get an unbiased opinion on this to see if there is a much simpler way of handling this type of "complex" conversion.
The most ideal outcome is to force coercion of the String into my desired type -- AnyObject. Via annotation or some very simple strategy.
But if I do have to deal with this via a Custom Deserializer, my preference would be to be able to get the handle to the in-process ObjectMapper in a clean way.
Thoughts?
Tried a few things and landed on the following by implementing a custom FasterJackson deserializer:
JsonNode node = jsonParser.readValueAsTree();
AnyObject val = jsonParser.getCodec().getFactory().createParser(node.asText().getBytes()).readValueAs(AnyObject.class);
I am still open to solutions that may be simpler or lean towards better performance with less intermediate garbage generation during parsing.
I have a json response coming from MongoDB and in its current form I have a pojo like below to bind these month field values:-
#JsonProperty("Feb-2017")
private Float feb2017;
The problem is that these month names change with time and those values will no longer be bound to the java object.The POJO in turn is an attribute of two other objects that represent this json. I cannot change the json structure in the Db and have tried creating this pojo at runtime following this answer but I cannot figure out how to reference this object across other POJOs .
Is there any other way I could approach this problem?
Thanks.
In your POJO, add a class member as follows:
private Map<String, Object> months = new HashMap<>();
Then create a method annotated with #JsonAnySetter:
#JsonAnySetter
public void set(String key, Object value) {
months.put(key, value);
}
This method works as a fallback handler for all unrecognized properties found in the JSON document.
I'm trying to save a copy of a class in generic Map<String, Object> form for an external data type:
Map<String, Object> test1 = objectMapper.convertValue(payoutBatch,
new TypeReference<Map<String, Object>>(){});
Map<String, Object> test2 = objectMapper.convertValue(payoutItemDetails,
new TypeReference<Map<String, Object>>(){});
PayoutBatch and PayoutItemDetails are both imported from the PayPal payments Java SDK and thus can't be annotated. But trying to convert them gives:
Class com.paypal.api.payments.PayoutBatch not subtype of
[map type; class java.util.Map, [simple type, class java.lang.Object] ->
[simple type, class java.lang.Object]]
I'm still pretty new to Jackson so is this even possible? Would like to know.
I assume that given a valid Map where Object in turn could be any primitive, or List or Map you know how to serialize it to JSON using Jackson, this is trivial. So I would suggest that you create an interface (say call it Mappable) with single method:
public Map<String, Object> toMap();
And then any class that you wish to serialize to JSON using Jackson should implement this interface. Each class would then implement a method that would produce the Map that represents your class and then you can pass this Map to serialization.
I have an Objectify entity that's failing to get registered.
Here's a stripped-down version of the class:
#com.googlecode.objectify.annotation.Entity
public class Insight {
#com.googlecode.objectify.annotation.Id long id;
public Map<Date, Double> timeseries;
}
Here is the error I'm getting:
java.lang.IllegalStateException: Embedded Map keys must be of type String/Enum/Key<?> or field must specify #Stringify
I believe the error is occurring during Objectify registration:
class OfyService {
static {
factory().register(Insight.class);
}
It's hard to tell though because there are lots of stack traces because the code is in a static block.
Objectify doesn't know what to do with a Date as a Map key. Map keys get turned into property names, which must be Strings. So you need to invent a DateStringifier class that implements the Stringifier interface. It's trivial, but you have some options - you can stringify your dates as millis-since-epoch or as some sort of human-readable format.
According to https://code.google.com/p/objectify-appengine/wiki/Entities#Maps, I must declare the Map in the following way because the Map's key is not a String:
#Stringify(com.googlecode.objectify.stringifier.KeyStringifier.class)
public Map<Date, Double> timeseries;
I have a Map<String, Object> which I am using as a mapping for a JSON document, however want to create and maintain Java type information at the same time as retaining the structure of the document.
I'm attempting to use Jackson to create the document and it seems to work fine but I'm seeing something strange when attempting to deserialize it. A very simple serialization example:
final ObjectMapper mapper = new ObjectMapper().enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, JsonTypeInfo.As.EXTERNAL_PROPERTY);
final Map<String, Object> map = Maps.newHashMap();
map.put("test", new Date());
final String ser = mapper.writeValueAsString(map);
final Map<String, Object> deser = mapper.readValue(ser, new TypeReference<HashMap<String, Object>>(){});
System.err.println(deser.get("test").getClass());
Gives the serialized form {"test":1410721662084,"#class":"java.util.Date"} which seems fine but when deserializing returns the type of "test" to be Long.
If I change the type serialization to use WRAPPER_ARRAY rather than EXTERNAL_PROPERTY then the type of "test" is correctly returned as Date, but doing this alters the structure of the JSON document so is not something I'm allowed to do. How do I retain the structure of the document as well as allow deserialization back to the correct types?
This is against Jackson 2.4.2.
Deserialization with maps is always tricky as maps don't preserve type information, which makes Jackson resort to #class and that not something you usually want. Instead, you can create a simple class:
public class TestClass {
private Date test;
//getters and setters omitted
}
This class has concrete structure and JSON will serialize it as
{ "test" : 1410721662084}
which is much cleaner and type-safe representation of your object. Then you just need to pass TestClass.class to readValue() method and your test attribute will be magically converted to proper type (Date)