In my application, I have objects created not by me, but by a Gson deserializer. These objects need references to singleton instances that everywhere else I am able to provide using constructor injection.
However, accessing the related component within the default constructor called by Gson like this
DaggerExampleComponent.builder().build().inject(this)
will not reuse the singletons injected everywhere else - from what I understood, this is because the builder will in fact create a new instance of ExampleComponent that does not know anything about the existing one.
My workaround is to keep a static instance field within ExampleComponent along with a getter, but I would like to know if there is a best practice of how to achieve the same thing with another approach.
EDIT The deserialization is being done on data retrieved from a database using the Android Room Persistence library. Converting data to custom objects is being implemented by using the #TypeConverter annotation on static methods, which are called implicitly when an element is retrieved from the database. This prevents me from injecting the created objects right there - the converters are static methods within a static class that is not instantiated, so I can not pass the DaggerComponent object to it to be used for injecting created instances, as suggested by Thorben below.
Disclaimer
I have not worked with Dagger in a long time. Take the following solutions with a grain of salt! The solution worked for me locally.
Without DaggerExampleComponent
One answer to you problem might be, to use a custom JsonDeserializer interface implementation, which takes the instance of the object you want to inject as an constructor argument.
You could write your own deserializer, that injects the singleton instance into the deserialized Object like this:
class MyJsonDeserializer implements JsonDeserializer<MyObject> {
private final MyComponent singleton;
public MyJsonDeserializer(MyComponent component) {
this.singleton = component;
}
public MyObject deserialize(JsonElement json, Type tye, JsonDeserializationContext context) throws JsonParseException {
// you could here parse some arguments
return new MyObject(singleton);
}
}
You would register it like this:
MyComponent component = ...
Gson gson = new GsonBuilder().registerTypeAdapter(MyObject.class, new MyJsonDeserializer(component)).create();
If you would have the MyComponent class be injected, you would ensure, that every created object has the same instance of the MyComponent object.
I personally would prefer this solution, to not mix up Dagger and Gson.
Using Dagger
You could as well change the code to use the DaggerAppComponent in said JsonDeserializer like this:
class MyJsonDeserializer implements JsonDeserializer<MyObject> {
private final DaggerAppComponent singletonProvider;
public MyJsonDeserializer(DaggerAppComponent componentProvdider) {
this.singletonProvider = componentProvider;
}
public MyObject deserialize(JsonElement json, Type tye, JsonDeserializationContext context) throws JsonParseException {
// you could here parse some arguments
MyObject object = ...
singletonProvider.inject(object);
return object;
}
}
and change the registration like this:
DaggerAppComponent componentBuilder = DaggerExampleComponent.builder().build();
Gson gson = new GsonBuilder().registerTypeAdapter(MyObject.class, new MyJsonDeserializer(componentBuilder)).create();
UPDATE
Because of the new information, i would suggest to enhance the existing class, that is used for the Android Room Persistence library (the class that contains the annotated method) like this:
class Convert {
static DaggerAppComponent singletonProvider;
#TypeConverter
public static MyObject convert(String arg) {
Gson gson = new GsonBuilder().registerTypeAdapter(MyObject.class, new MyJsonDeserializer(componentBuilder)).create();
return gson.fromJson(arg, MyObject.class);
}
#TypeConverter
public static String fromArrayLisr(MyObject object) {
Gson gson = new Gson();
String json = gson.toJson(v);
return json;
}
}
I took some inspiration from thetechguru. Also, this asumes the same JsonDeserializer that is stated above.
Since i do not know the acutal parameters, i asume String as the argument to this typeconverter. Insert you corresponding type there.
To use this effectivly, somewhere in the code (before any database related stuff is done), this should be called:
Convert.singletonProvider = DaggerExampleComponent.builder().build();
This will allow the Convert class to see the correct DaggerAppComponent.
There might still be an issue with this. This is a race condition, concering the null state of the static variable. If the database is called to soon, the result will be a NullpointerException, since the static field has not been set yet. To counteract this, you could use a Semaphore (or anything semilliar), to create some sort of barrier. With a Semaphore, this would include a simple Semaphore that has 0 permits. Before using the variable calling acquire and release on it. After the variable has been set (outside of this class), calling release on it once.
This is not a good solution (in terms of software design), but it should do the trick.
Related
During deserialization, how can I pass in an extra object that's needed to initialize some class member? If I were doing deserialization "manually," the implementation might look like:
public class MyClass {
private MyDocumentObject do;
private String food;
public MyClass(JsonNode node, MyDocument document) {
this.do = document.createMyDocumentObject();
this.food = node.get("food").asText();
}
public String getFood() {
return this.food;
}
}
But I'd like to use Jackson's automatic mapping facilities and use a decorated constructor or custom deserializer, etc. and avoid implementing the deserialization within the class itself. Looking at example implementations using #JsonCreator or extending StdDeserializer, I can't see a way of saying "hey, please use this MyDocument object when you call the constructor." I'd like to avoid implementing and exposing a separate method that accepts a MyDocument that I have to invoke on every object that gets deserialized, e.g.
public createDocumentObject(MyDocument document) {
this.do = document.createMyDocumentObject();
}
I don't want to have this method at all, but if I had to, I'd want Jackson to call this method for me right after deserialization. That means I'd still have to somehow tell Jackson which MyDocument to use.
I know Gson doesn't come with a similar feature, but is there a way to add support for unwrapping Json fields the way #JsonUnwrap does?
The goal is to allow a structure like:
public class Person {
public int age;
public Name name;
}
public class Name {
public String first;
public String last;
}
to be (de)serialized as:
{
"age" : 18,
"first" : "Joey",
"last" : "Sixpack"
}
instead of:
{
"age" : 18,
"name" : {
"first" : "Joey",
"last" : "Sixpack"
}
}
I understand it could get fairly complex, so I'm not looking for a full solution, just some high-level guidelines if this is even doable.
I've made a crude implementation of a deserializer that supports this. It is fully generic (type-independent), but also expensive and fragile and I will not be using it for anything serious. I am posting only to show to others what I've got, if they end up needing to do something similar.
public class UnwrappingDeserializer implements JsonDeserializer<Object> {
//This Gson needs to be identical to the global one, sans this deserializer to prevent infinite recursion
private Gson delegate;
public UnwrappingDeserializer(Gson delegate) {
this.delegate = delegate;
}
#Override
public Object deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
Object def = delegate.fromJson(json, type); //Gson doesn't care about unknown fields
Class raw = GenericTypeReflector.erase(type);
Set<Field> unwrappedFields = ClassUtils.getAnnotatedFields(raw, GsonUnwrap.class);
for (Field field : unwrappedFields) {
AnnotatedType fieldType = GenericTypeReflector.getExactFieldType(field, type);
field.setAccessible(true);
try {
Object fieldValue = deserialize(json, fieldType.getType(), context);
field.set(def, fieldValue);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
return def;
}
}
It can then be registered globally via new GsonBuilder().registerTypeHierarchyAdapter(Object.class, new UnwrappingDeserializer(new Gson())).create() or for a specific type via registerTypeAdapter.
Notes:
A real implementation should recursively check the entire class structure for the presence of GsonUnwrap, cache the result in a concurrent map, and only go through this procedure if it needs to. Otherwise it should just return def immediately
It should also cache discovered annotated fields to avoid scanning the hierarchy each time
GenericTypeReflector is coming from GeAnTyRef
ClassUtils#getAnnotatedFields is my own implementation, but it doesn't do anything special - it just gathers declared fields (via Class#getDeclaredFields) recursively for the class hierarchy
GsonUnwrap is just a simple custom annotation
I presume a similar thing can be done for serialization as well. Examples linked from Derlin's answer can be a starting point.
Currently, there is no easy way to do that. Here are anyway some pointers/alternative ways to make it work.
GsonFire: GsonFire implements some useful features missing from Gson. While it does not yet offer automatic wrapping/unwrapping, it may be a good starting point to create your custom logic.
If you only need serialization, you can add getters for first and last in Person and use #ExposeMethodResult to serialize them. Unfortunately, setters are not supported (cf. Is possible to use setters when Gson deserializes a JSON?).
Another way to support the serialization is to follow the advices from How to move fields to parent object.
Custom TypeAdapters : on of the only ways to support both serialization and deserialization is to create custom TypeAdapters. This won't be generic, but it will suit your usecase.
The thread Serialize Nested Object as Attributes already gives you examples, so I won't repeat them here.
I have a POJO with a field marked as transient. GSON does not serialize it. Great. But when it is deserialized it wipes out the the field's initial settings.
For example, if I have this object:
public class ObjWithTransient {
public String name;
public transient List<String> listOStrings = new ArrayList();
}
And I run this test:
#Test
public void testSerializeWithTransient() throws Exception {
ObjWithTransient obj = new ObjWithTransient();
obj.name = "Foobar";
String json = gson().toJson(obj);
// Deserialize
ObjWithTransient obj2 = GsonUtil.gson().fromJson(json, ObjWithTransient.class);
Assert.assertEquals(obj2.name, "Foobar");
Assert.assertNotNull(obj2.listOStrings); // <--- Fails here
Assert.assertEquals(obj2.listOStrings.size(), 0);
}
By marking it transient, I assume I am telling GSON to ignore it, but that doesn't seem to be the case. What is the best way to retain the initial settings here?
EDIT:
I believe the issue is because there is not a declared constructor. This does not work with an inner class, but with a normal class or a static inner class it appears to work. Reading the GSON code, it trys multiple ways to create the object, but ultimately uses a UnsafeConstructor to create it if nothing else works. This creates an object with null entries across the board. I could also add an InstanceCreator to tell Gson how to create the object.
I believe the issue is because there is not a declared constructor. This does not work with an inner class, but with a normal class or a static inner class it appears to work. Reading the GSON code, it trys multiple ways to create the object, but ultimately uses a UnsafeConstructor to create it if nothing else works. This creates an object with null entries across the board. I could also add an InstanceCreator to tell Gson how to create the object.
Is there any way the set methods of a given class, are used when using Gson's fromJson method?
I would like to do this because for every String global variable of the target class a trim is made.
Is there any GSON API annotation for this?
I am aware that GSON provides the ability to write custom serializers/deserializers but I would like to know if there is another way to achieve this.
No, there is not. Gson works mainly by reflection on instance fields. So if you do not plan to move to Jackson that has this feature I think you cannot have a general way to call your setters. So there's no annotation for that.
BUT
to achieve your specific need you could:
write your own custom TypeAdapter or
create a constructor that has the string you intend to trim and create a custom InstanceCreator or
parse your JSON as JsonObject, do some processing of the strings and then use that object as source for parsing into your class.
I can provide you with more hints as long as you post some code or give information about your data/JSON.
I implemented a JsonDeserializer<String> and registered it on GsonBuilder. So, to all String fields received, Gson will use my StringGsonTypeAdapter to deserialize the value.
Below is my code:
import static net.hugonardo.java.commons.text.StringUtils.normalizeSpace;
import static net.hugonardo.java.commons.text.StringUtils.trimToNull;
final class StringGsonTypeAdapter implements JsonDeserializer<String> {
private static final StringGsonTypeAdapter INSTANCE = new StringGsonTypeAdapter();
static StringGsonTypeAdapter instance() {
return INSTANCE;
}
#Override
public String deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
return normalizeSpace(trimToNull(jsonElement.getAsString()));
}
}
...and my GsonBuilder:
Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, StringGsonTypeAdapter.instance())
.create())
In one of our projects we use a java webapp talking to a MongoDB instance. In the database, we use DBRefs to keep track of some object relations. We (de)serialize with POJO objects using jackson (using mongodb-jackson-mapper).
However, we use the same POJOs to then (de)serialize to the outside world, where our front end deals with presenting the JSON.
Now, we need a way for the serialization for the outside world to contain the referenced object from a DBRef (so that the UI can present the full object), while we obviously want to have the DBRef written to the database, and not the whole object.
Right now I wrote some untested static nested class code:
public static class FooReference {
public DBRef<Foo> foo;
// FIXME how to ensure that this doesn't go into the database?
public Foo getFoo() {
return foo.fetch();
}
}
Ideally I would like a way to annotate this so that I could (de)serialize it either with or without the getFoo() result, probably depending on some configuration object. Is this possible? Do you see a better way of going about doing this?
From looking at options, it seems you can annotate properties to only be shown if a given View is passed to the ObjectMapper used for serialization. You could thus edit the class:
public static class FooReference {
public DBRef<Foo> foo;
#JsonView(Views.WebView.class)
public Foo getFoo() {
return foo.fetch();
}
}
and provide:
class Views {
static class WebView { }
}
and then serialize after creating a configuration with the correct view:
SerializationConfig conf = objectMapper.getSerializationConfig().withView(Views.WebView.class);
objectMapper.setSerializationConfig(conf);
Which would then serialize it. Not specifying the view when serializing with the MongoDB wrapper would mean the method would be ignored. Properties without a JsonView annotation are serialized by default, a behaviour you can change by specifying:
objectMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
More info is available on the Jackson Wiki.
There are still other alternatives, too, it turns out: there are Jackson MixIns which would let you override (de)serialization behaviour of parts of a class without modifying the class itself, and as of Jackson 2.0 (very recent release) there are filters, too.
Use a custom JSONSerializer and apply your logic in the serialize method:
public static class FooReference {
public DBRef<Foo> foo;
#JsonSerialize(using = CustomSerializer.class)
public Foo getFoo() {
return foo.fetch();
}
}
public class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
// jgen.writeObjectField ...
}
}