I'm using GSON for deserializing some JSON data and I'm interested in a way to pass some contextual values into the deserialization process. To be more specific, suppose we have 2 classes:
class A {
String relativePath;
transient B contextDependentValue;
}
class B {
int y;
A z;
}
and a JSON as follows:
{
y: 2,
z: {
relativePath: "./foo/bar"
}
}
When trying to deserialize things, I'd like to populate the contextDependentValue field in the nested object with something that is dependent on where the context of deserialization (e.g. contextDependentValue could be the absolute path on which the JSON was found, so I could then build the full path as contextDependentValue + '/' + relativePath). These context values would be set for each deserialization.
Ideally, I would be able to build a custom JsonDeserializer that gets a context values holder when asked to deserialize:
public T deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context,
Map<String, Object> someSortOfContextValuesHolder) {
return buildObjectWithContext(json, someSortOfContextValuesHolder);
}
and where someSortOfContextValuesHolder would be provided when starting each deserialization:
gson.fromJson(json, B.class, someSortOfContextValuesHolder)
Any ideas on how I could implement something like this?
Have a look at Gson's TypeAdaptor
By default Gson converts application classes to JSON using its built-in type adapters. If Gson's default JSON conversion isn't appropriate for a type, extend this class to customize the conversion.
This stackoverflow article about how-to-handle-deserializing-with-polymorphism also shows sample code samples which may prove relevant and help you out a bit.
Should you be interested in marshalling at runtime you should look into RuntimeTypeAdapterFactory
Adapts values whose runtime type may differ from their declaration type.
...and these article about convert-from-json-to-multiple-unknown-java-object-types-using-gson and how-to-deserialize-a-list-of-polymorphic-objects-with-gson might also prove useful.
Related
I am parsing JSON string from a byte-array and casting it as an object.
How do I determine the class of the object?
Object objDeserialized = gson.fromJson(jsonFromString, Object.class);
//It could be type Message or RoomDetail
gson.fromJson(jsonFromString, Object.class);
In general, this won't work because of Object.class. Gson prohibits overriding the Object class deserialization and uses ObjectTypeAdapter (see the primary Gson constructor as of Gson 2.8.0 and probably much earlier):
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
// the excluder must precede all adapters that handle user-defined types
factories.add(excluder);
// user's type adapters
factories.addAll(typeAdapterFactories);
If you want to use Object.class, you have to cast the result to either a primitive wrapper, null, or a List<E> or Map<K,V> -- and make some sort of analysis yourself. The rationale behind it is that you must know the result class in advance to make sure you're getting a proper deserialized object.
The best thing you can do here is making your custom parent super-type (does not really matter if it's a class or an interface), say class Message extends Base and class RoomDetail extends Base, and then registering a JsonDeserializer<Base> implementation to a GsonBuilder which can attempt to detect the real type of the Base instance. After that you can do:
gson.fromJson(jsonSource, Base.class);
See more:
Polymorphic objects deserialization:
How to parse dynamic json in android with retrofit 2 using annotations
How do I parse a nested JSON array of object with a custom Gson deserializer?
Json response parser for Array or Object
Google Gson extras, never been published as artifacts, but may be an inspiration point for you:
https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
If you do not know the type of the JSON you want to parse you could use the JsonParser from the Gson lib to parse the JSON instead of the Gson class directly. e.g.
JsonParser parser = new JsonParser(jsonFromString);
JsonObject obj = parser.parse().getAsJsonObject();
You could then look at the properties of the JsonObject you have created to see what it is. e.g.
if (obj.has("somePropertyNameIKnownIsAMemberOfRoomDetail")) {
RoomDetail roomDetail = gson.fromJson(jsonFromString, RoomDetail.class);
} else {
Message message = gson.fromJson(jsonFromString, Message.class);
}
I'm trying to deserialize using ObjectMapper to a POJO and im getting the error:
No suitable constructor found for type [simple type, class LambdaResult<java.lang.Object>]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
My Pojo is quite simple
class LambdaResult<T> {
LambdaResult() {}
String Status
ArrayList<T> Results
}
And my deserialization code is the following
static <T> T Deserialize(final TypeReference<T> type,
final String json) {
return new ObjectMapper().readValue(json, type)
}
LambdaResult<Object> result = Serialization.Deserialize(new TypeReference<LambdaResult<Object>>() {},jsonResult)
Json example:
{"status": "success", "locale": "sg", "results": [{"status": "pending"}]}
I come from a C# background so there's something im probably missing here.
Thanks
I would recommend creating and configuring an ObjectMapper like this (Java, sorry don't speak Groovy):
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper in this case should probably be a field in the same class as Deserialize method that would then look like:
return mapper.readValue(json, type);
Explanation for configuration:
This MapperFeature tells Jackson to match field names in a case insensitive manner. So it will find String Status although it doesn't follow Java Beans naming conventions. This is neat in your case but comes with a small performance penalty for transforming a String to lowercase. You can alternatively annotate a field in a class with #JsonProperty and specify any JSON field name you want.
This tells Jackson to match JSON fiels to Java object fields regardless of field visibility private, public etc. You may also annotate your class with #JsonAutoDetect and specify visibility there. In both cases you don't need to define a constructor but you can keep it if you don't want it to be public.
The last one tells Jackson to not fail when encountering fields in JSON that don't exist in your class. In this case "locale".
I have what I believe should be a simple use case.
I would like to serialize a POJO with type metadata (preferably a simple name I come up with, not the fully qualified class/package name), and later have Jackson deserialize the JSON back into the concrete class it came from by using this metadata. There is no inheritance hierarchy among classes being serialized and deserialized.
My scenario is I have a service which accepts multiple file types. For each file uploaded, the client can retrieve JSON data whose structure and type depends on the file it came from. Thus when I retrieve JSON from the service, it's not known what the concrete class is to deserialize to. I would like Jackson to figure this out based on metadata which it supplies.
For example, I'd like to be able to do this:
String json = ... // get JSON from the service
Object obj = mapper.readValue(json, Object.class) // concrete class is not known
System.out.println(obj.getClass()) // I want this to be MyConcreteClass.class
There is no inheritance hierarchy among JSON types returned.
I don't want to reveal package names or other internal service
details/structure.
I have control over Jackson's serialization process
Relevant question: Can jackson determine root object type to deserialize to when json includes type property?
Thank you so much for your help!
This can be achieved using Jackson's JavaType:
String className = "class.name.from.json.service";
JavaType dtoType = TypeFactory.defaultInstance().constructFromCanonical(className);
Object dto = new ObjectMapper().readValue(InputStream, dtoType);
assert dto.getClass().equals(dtoType.getRawClass());
When deserializing a variety of JSON messages, I want to provide a default value for attributes of a certain type. It is generally suggested to simply specify the value in the Class, but this is error-prone if you have to do this across many Classes. You might forget one and end up with null instead of a default value. My intention is to set every property that is an Optional<T> to Optional.absent. Since null is exactly what Optional is trying to eliminate, using them with Jackson has proven to be frustrating.
Most features of Jackson that allow you to customize the deserialization process focus on the JSON that is the input, not around the process of instantiating the Object that you are deserializing into. The closest I seem to be getting to a general solution is by building my own ValueInstantiator, but there are two remaining issues I have:
how do I make it only instantiate Optional as absent but not interfere with the rest of the instantiation process?
how do I wire the end result into my ObjectMapper?
UPDATE: I want to clarify that I am looking for a solution that does not involve modifying each Class that contains Optional's. I'm opposed to violating the DRY principle. Me or my colleagues should not have to think about having to do something extra every time we add Optional's to a new or existing Class. I want to be able to say, "make every Optional field in every Class I deserialize into, pre-filled with Absent", only once, and be done with it.
That means the following are out:
abstract parent class (need to declare)
custom Builder/Creator/JsonDeserializer (needs annotation on each applicable class)
MixIn's? I tried this, combined with reflection, but I don't know how to access the Class I'm being mixed into...
Specifically for java.lang.Optional, there is a module by the Jackson guys themselves: https://github.com/FasterXML/jackson-datatype-jdk8
Guava Optional is covered by https://github.com/FasterXML/jackson-datatype-guava
It will create a Optional.absent for null's, but not for absent JSON values :-(.
See https://github.com/FasterXML/jackson-databind/issues/618 and https://github.com/FasterXML/jackson-datatype-jdk8/issues/2.
So you're stuck with initializing your Optionals just as you should initialize collections. It is a good practice, so you should be able to enforce it.
private Optional<Xxx> xxx = Optional.absent();
private List<Yyy> yyys = Lists.newArrayList();
You can write a custom deserializer to handle the default value. Effectively you will extend the appropriate deserializer for the type of object you are deserializing, get the deserialized value, and if it's null just return the appropriate default value.
Here's a quick way to do it with Strings:
public class DefaultStringModule extends SimpleModule {
private static final String NAME = "DefaultStringModule";
private static final String DEFAULT_VALUE = "[DEFAULT]";
public DefaultStringModule() {
super(NAME, ModuleVersion.instance.version());
addDeserializer(String.class, new DefaultStringDeserializer());
}
private static class DefaultStringDeserializer extends StdScalarDeserializer<String> {
public DefaultStringDeserializer() {
super(String.class);
}
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
String deserialized = jsonParser.getValueAsString();
// Use a default value instead of null
return deserialized == null ? DEFAULT_VALUE : deserialized;
}
#Override
public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
return deserialize(jp, ctxt);
}
}
}
To use this with an ObjectMapper you can register the module on the instance:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new DefaultStringModule());
To handle default values for fields not present in the JSON, I've typically seen this done through the use of a builder class that will construct the class using the values supplied and add any default values for the missing fields. Then, on the deserialized class (e.g. MyClass), add a #JsonDeserialize(builder = MyClass.Builder.class) annotation to indicate to Jackson to deserialize MyClass by way of the builder class.
Your value object should initialize these values as absent. That's the way to ensure that default values have no nulls. Guava module's Optional handler really should only deserialize them as "absent" (even with explicit JSON nulls), and never as nulls, with later versions.
But since Jackson only operates on JSON properties that exist (and not on things that could exist but do not), POJO still needs to have default absent assignment.
I'm building a data driven test system. I have done this before in XML but json is giving me some interesting issues.
For each request and response type json, I have a setting in my script where I specify a pojo type. This type is instantiated to a class object thats passed to jackson to marshal the json into a usable pojo. so its like this:
"responseType": "java.util.List",
eventually gets pumped to
Class<?> reponseType = null;
try {
if (d.shouldPass) {
reponseType = Class.forName(d.responseType);
}
} catch (ClassNotFoundException e) {
throw new RequestResponseTypeInvalid(testName);
}
and I have usable class info to use in jackson. My problem is I need to do this:
"responseType": "java.util.List<foo>",
otherwise complex json types parse as hashmaps instead of pojo's. I suppose I can get creative and put something in to go from hashmap to pojo if I need to but I was wondering if there was any straight forward way to do this.
I suppose another way is to implement a factory class where I could say list_foo in the property file and have the factory class map that to an actual class object. That wouldn't be very hard but not as easy as just using the property.
thanks
You can't do this in the way that you're hoping, I'm afraid. Generics are a compile-time thing only, and can't be used in this way at runtime, because of type erasure.
The best you could do would be to have some list_foo properties, and map these explicitly to List<Foo> and so in in your code. But you can't do it by reflection.