Is possible to use setters when Gson deserializes a JSON? - java

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())

Related

Gson does not correctly serialize LocalDate

I am writing an android application, where I want to serialize instances of this Anime.java class. Its superclass AnimeBase.java has a field called aired, which is of the type DateRange. This DateRange contains two fields:
public LocalDate from;
public LocalDate to;
The serialization is very straight-forward (using gson) like this:
final Gson gson = new Gson();
String data = gson.toJson(obj);
However, in my result, the from and to fields are always empty like here:
// ...
"trailer_url": "https://www.youtube.com/embed/SlNpRThS9t8?enablejsapi\u003d1\u0026wmode\u003dopaque\u0026autoplay\u003d1",
"aired": {
"from": {}
},
"episodes": 16,
// ...
Here, to was null, so it is missing (and that is okay).
Why is gson not serializing these two LocalDates? Does it have something to do with the DateRanges setter & getter (which are a bit unusual, taking a OffsetDateTime instead of a LocalDate)?
Since these classes stem from a 3rd-party library, is there a good way for me to handle this without duplicating all the model classes in my own application for serializing/deserializing them?
Take a look at https://github.com/gkopff/gson-javatime-serialisers
There are serializers for LocalDate objects.
If you choose to create your own serializer:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(new TypeToken<LocalDate>(){}.getType(), new LocalDateConverter());
Gson gson = builder.create();
...
public class LocalDateConverter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(DateTimeFormatter.ISO_LOCAL_DATE.format(src));
}
public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return DateTimeFormatter.ISO_LOCAL_DATE.parse(json.getAsString(), LocalDate::from);
}
}
I could now find the source of this problem.
Starting in Android 9, Google added something called "Restrictions on non-SDK interfaces" where they restrict the access to not publicly documented SDK interfaces in the Android dalvik runtime.
Since Gson by default uses the ReflectiveTypeAdapterFactory which itself looks for serializable fields in the object to be serialized it heavily depends on Reflection.
Google has documented this behaviour, that the used function Class.getDeclaredFields() which is used by ReflectiveTypeAdapterFactory do return only public accessible Fields, or more concrete, only fields which are whitelisted by Google.
https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces#results-of-keeping-non-sdk
In the referenced documentation, Google explicitly states the java.time.LocalDate fields as greylisted:
Ljava/time/LocalDate;->day:S,greylist-max-o
I am not sure why this access is still working in release mode and the behaviour only is present when the build is debuggable but I suppose this is something which will be removed in future Android versions too.
Because of this we added our own backwards-compatible Serializer (which is similar to the one of #k1r0, but still works with previously serialized values):
class LocalDateJsonSerializer : JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
override fun serialize(src: LocalDate, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonObject().also {
it.addProperty("year", src.year)
it.addProperty("month", src.monthValue)
it.addProperty("day", src.dayOfMonth)
}
}
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): LocalDate {
val jsonObject = json.asJsonObject
return LocalDate.of(jsonObject["year"].asInt, jsonObject["month"].asInt, jsonObject["day"].asInt)
}
}

Dependency injection with Dagger during Gson deserialization

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.

GSON serialisation - how to exclude some fields from serialisation, but not deserialisation

If I have a type "Person", and it has multiple fields, including "password", then how do I tell GSON that I want accept the password field when it's passed in, but not to pass it back out?
Specifically, in this case, it's because my web front end can be used to update the password and send it to the Java side,, but I never want to send the password back to the front end (for obvious security reasons).
I am not sure you can do it with Gson, but you could with Genson. Put #JsonIgnore(deseriaize=true) on your getPassword method.
Or if you want genson to use only fields instead of public getter/setter and fields, configure it like that:
Genson genson = new Genson.Builder()
.setUseGettersAndSetters(false)
.setFieldVisibility(VisibilityFilter.DEFAULT)
.create();
In that case put the annotation on the field.
You can deserialize your class as usual (since you want to deserialize all the fields) and write a custom serializer that excludes the password. Something like this:
public class PersonSerializer implements JsonSerializer<Person> {
#Override
public JsonElement serialize(Person src, Type typeOfSrc, JsonSerializationContext context)
{
JsonObject obj = new JsonObject();
obj.addProperty("name", src.name);
obj.addProperty("gender", src.gender);
obj.addProperty("age", src.age);
//And all the other fields but the password...
return obj;
}
}
Then you just need to register the serializer with:
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(Person.class, new PersonSerializer());
And finally serialize your object as usual with gson.toJson method...
I'm not sure if it's the best approach, but it's pretty straightforward... Otherwise you can take a look at Gson's excusion strategies...

Serialize an object which has a json-format-string to json

I have an object like:
public class Foo{
public String f1="[{\"jsonField\":\"something\"},{\"jsonField\":\"something\"}]";
}
Gson would serialize it to:
{"f1":"[{\"jsonField\":\"something\"},{\"jsonField\":\"something\"}]"}
The f1 field was serialized to a string. Apparently, the field is a well formed json-format string. How can I do to serialized the field to an jso array, like below:
{"f1":[{"jsonField":"something"},{"jsonField":"something"}]}
PS. For performance consideration, I can't deserialize then serialize the field.
Gson is serializing your string field to a string. That's normal. If your field, however, was a List<String>, Gson would give you:
{"f1":["jsonField:something","jsonField:something"]}
And if it was a List where something has a field called jsonField you'd get what you actually want.
This is the implicit way that Gson serializes your objects. If you don't like it, you need to implement your own TypeAdapter and register it with Gson at the builder.
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyObject.class, new SomethingGsonAdapter());
Gson gson = builder.create()
And you need to define:
public class SomethingGsonAdapter implements JsonDeserializer<DataItem>, JsonSerializer<DataItem>;

JSON Serialize / Deserialize generic type with google-gson

Well, I have to confess I'm not good at generic type in Java
I've written a JSON serialize/deserialize class in C# using JavaScriptSerializer
private static JavaScriptSerializer js = new JavaScriptSerializer();
public static T LoadFromJSONString<T>(string strRequest)
{
return js.Deserialize<T>(strRequest);
}
public static string DumpToJSONString<T>(T rq)
{
return js.Serialize(rq);
}
It works well in C#. Now I'm trying to convert, or atleast, write another JSON serialize/deserialize class in Java. I've tried flexjson and google-gson but I don't know how to specify <T> in Java.
Could someone here help me? Btw, I prefer google-gson
In Java you must pass actual type-erased class, not just type parameter, so data binders know what type to use; so something like:
public static T LoadFromJSONString<T>(string strRequest, Class<T> type)
{
Gson gson = new Gson();
return gson.fromJson(strRequest, type);
}
but this is usually just needed for deserialization; when serializing, you have an instance with a class which libraries can use.
Btw, one other good Java JSON library you might want to consider using is Jackson; however, for this particular example all libraries you mention should work.
After thinking this through I don't think there is a way to solve this as pretty as it is done in your C# code. This because of the type erasure done by the java compiler.
The best solution for you would probably be to use the Gson object at the location where you know the type of the object you need to serialize/deserialize.
If you're not keen on making instances of the Gson object everytime you can of course keep that one static at least, since it doesn't need any type parameters when created.
This should work:
import com.google.gson.Gson;
public class GenericClass {
private static Gson gson = new Gson();
public static <T> T loadFromJSONString(String strRequest)
{
return (T) gson.fromJson(strRequest, T.class);
}
public static <T> String dumpToJSONString(T rq)
{
return gson.toJson(rq);
}
}

Categories