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.
Related
If I have a class
class DTO {
final MySet<Types> values = MySetWrapper(EnumSet.of(Types.class));
public MySet getValues() {
return values;
}
}
where MySet extends Set. Jackson complains that
Cannot find a deserializer for non-concrete Collection type MySet
which I understand, but I already instantiated the collection. What I want is for jackson to just call add for each value after it created an instance, something like:
DTO o = new DTO();
MySet<Types> values = o.getValues();
for (Types type : jsonArray) {
values.add(type );
}
I don't want it to try to create a new collection itself.
That error message means that the DTO class is configured (by default or explicitly) to deserialize the values part of the JSON input into the DTO values field of DTO :
Cannot find a deserializer for non-concrete Collection type MySet
If you consider that Jackson should not perform the deserialization directly on this field, you could define a constructor to set values and also make sure that Jackson will not perform automatically the deserialization work : to achieve it, remove setter for that field (or add #JsonIgnore on it) and any jackson module that will use reflection to deserialize to fields.
It would give :
final MySet<Types> values = MySetWrapper(EnumSet.of(Types.class));
#JsonCreator
public MyFoo(Set<Types> values) {
this.values.addAll(values);
}
Note that I specified in the constructor Set and not MySet (should not be an issue as interface doesn't declare fields), otherwise you would get the same issue since you didn't define a deserializer for MySet.
But if you implement a deserializer for that you could of course do :
public MyFoo(MySet<Types> values) {
this.values.addAll(values);
}
Found an answer using #JsonProperty:
#JsonProperty
private void setValues(Set<Types> types) {
values.addAll(types);
}
Pretty short and simple thankfully.
Edit: seems like you don't even need the annotation.
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.
Problem Statement
I want to enforce such a situation that whenever the instance of the immutable pojo is to created all the mandatory variables must be passed in as parameters.
Let's Suppose I have Pojo like this
public class pojo {
private final String str;
private int number;
}
Now, As I said the pojo is immutable, I am using Lombok's Builder and Getter annotation.
Something Like,
#Getter
#Builder
public class pojo {
private final String str;
private int number;
}
Now, In this pojo the variable str is mandatory, and any instance of class pojo without a valid value of str is of no requirement.
Now, Object of class Pojo can be constructed using Builder in such a way
pojo obj = pojo.builder().build();
But the field str is mandatory and the above statement will create an object with str = null and number = 0. Now i can make use of #NonNull over
str to ensure that Object creation throws a Null pointer Exception at runtime if someone tries to build the object of pojo without specifying str.
But I want to enforce this check at compile time, Is there a way to stop object creation if str is not given as paramter?
You can do this if you write your Builder by hand. Have your builder() factory method return a class like UnreadyBuilder which doesn't have a build() method. Then the setName() setter (or whatever it's called) on UnreadyBuilder can return a ReadyBuilder object which does have a build() method.
That said: do you really need this to be checked at compile time? It's a lot of extra code for a pretty marginal benefit.
this is really basic I know, but I just can't see what the problem is... all I want to do is set the value of a variable from one class into an "intermediary" class and retrieve it in a third class (because filterArray will get called from other classes as well, and I want them all to read the same data). But if I do:
b =new GetSet()
b.setBdl(extras);
JSONArray arr= getData.filterArray();
using
class GetSet {
private Bundle params;
public GetSet() {
}
public Bundle getBdl() {
return this.params;
}
public void setBdl(Bundle bdl) {
params = bdl;
}
}
then in the filterArray method, if I try
Bundle params = new GetSet().getBdl();
I get all sorts of run time errors, and if I try
Bundle params = GetSet.getBdl();
it tells me I can't make a static reference to a non-static method.
Where am I going wrong?
The errors are because you are using it in a wrong way
look at your signature of getBdl its a public Bundle method its a non static method so can't be accessed by the classname.
it should be accessed through the bean object that is b,
second error is also related,
you wrote/set the property of the bean with the object b but when you access it , you are again creating a new GetSet().getBdl so it says Null pointer exception
so do like this
GetSet b=new GetSet();
b.setBdl(bundle object);
so now b contains the value
pass the b...
so get the bundle through b,,if you need to access it from another class pass b into its constructor and get it their
SomeClass class=new SomeClass(b);
now in that class you can get the bundle via b
hope it all helps you.
You should pass b to filterArray method.
First change filterArray to get one parameter of type GetSet:
public JSONArray filterArray(GetSet b)
Then, call it like:
getData.filterArray(b);
And inside filterArray just use:
Bundle params = b.getBdl();
The problem is that you create an object and sets a value, then you need to get the value from the same object.
When working with objects, you can create a new object of whatever class you want, set its internal value and when you access the same object from different places, the internal value will be the same (Im talking here both on GetSet and getData.
Another option is to use static value (and static getters and setters), if the value is same for all objects of the class. This usually not needed and may be used due to bad design. In your case you don't really need it, but it will work.
I am writing a function which serialize a Java object into Json using Gson.
The problem I have is that it only serialize primitive fields of my class but not object fields. For example. I have two classes like:
class TestClass {
public int i = 10;
public TestClass2 tc2;
}
class TestClass2 {
public int j = 20;
}
And my test is:
#Test
public void shouldSerializeSimpleObjectIntoJson() {
TestClass tc = new TestClass();
String json = new Gson().toJson(tc);
System.out.println(json);
}
The Json output is:
{"i":10}
It does not contain the fields of tc2.
How can I config Gson to recursively encode an object into Json?
Thanks
The field is ignored because it's null. From the documentation:
The default behaviour that is implemented in Gson is that null object fields are ignored. This allows for a more compact output format; however, the client must define a default value for these fields as the JSON format is converted back into its Java.
Here's how you would configure a Gson instance to output null:
Gson gson = new GsonBuilder().serializeNulls().create();
If you set tc.tc2 to a value other than null, you should see its fields in the output file.
With your specific example, add a constructor which will make a new instance of tc2:
public TestClass()
{
tc2 = new TestClass2();
}
and see if it helps. If in your real code the class which is similar to TestClass2 has reference classes as well, create a new instance of them as well
In Java, object fields are not initialized by default, that means the tc2 field will be null unless you set it to something. Gson ignores null fields by default, and thus it's not serializing it.
You could initialize the field in the constructor, like the other answer correctly says. Or you could also initialize it doing the following:
class TestClass {
public int i = 10;
public TestClass2 tc2 = new TestClass2();
}