I have an ArrayList<CustomClass> inside of ParentClass which I have written to a file using Gson.toJson(). However when I try to de-serialize the JSON using Gson.fromJson() I only get 1 element of the ArrayList<CustomClass>.
For example I will do the following
public class ParentClass {
private ArrayList<CustomClass> myList = new ArrayList<CustomClass>();
private GrandParentClass nested;
public ParentClass() {
myList.add(new CustomClass("adsf"));
myList.add(new CustomClass("fdsa"));
nested = new GrandParentClass();
}
public int arraySize() {
return myList.size();
}
}
public class GrandParentClass {
private ArrayList<OtherCustomClass> myList = new ArrayList<OtherCustomClass>();
public GrandParentClass() {
myList.add(new CustomClass("asdfasdf.."));
myList.add(new CustomClass("fdsafdsa..."));
}
public int arraySize() {
return myList.size();
}
}
Then when I instantiate a new instance of ParentClass, I use the following to write it to a file.
ParentClass pc = new ParentClass();
Gson gson = new Gson();
String writeThis = gson.toJson(pc); // Produces a perfect JSON reflection myList
FileOutputStream fos = new FileOutputStream(new File("writeto.json"));
fos.write(writeThis);
fos.close();
The JSON object is written in plain text to the .json file
FileInputStream fis = new FileInputStream(new File("writeto.json"));
char c;
StringBuffer sb = new StringBuffer();
while ((c = fis.read()) != -1)
sb.append((char) c);
//Now this is where I only get 1 element of the ArrayList
Gson gson = new Gson();
ParentClass pc = gson.fromJson(sb.toString(), ParentClass.class);
Log.i("SIZE", "Size is " + pc.arraySize()); // Log output: 'Size is 1'
Now, even though I have verified that there are indeed two elements in the ArrayList in the JSON file, only 1 gets loaded into the object using fromJson.
I am serializing these just fine, I would however like to deserialize the ArrayList<OtherCustomClass> inside of the GrandParentClass, which is inside of the ParentClass, in one fell swoop.
Basically I want to serialize ArrayLists nested possibly 3 or 4 layers down in this object heirarchy, and deserialize them into 1 ParentClass that contains these nested ArrayLists<?>. How would this be accomplished?
Thanks
You need to do some additional work when you want to deserialize a collection when generics are involved. This is explained here.
However, I am not sure how this applies to your case, since you have the collection "nested" within a top-level non-generic class.
public class ParentClass {
public static ArrayList<CustomClass> myList = new ArrayList<CustomClass>();
private GrandParentClass nested;
public ParentClass() {
myList.add(new CustomClass("adsf"));
myList.add(new CustomClass("fdsa"));
nested = new GrandParentClass();
}
public int arraySize() {
return myList.size();
}
}
public class GrandParentClass {
public GrandParentClass() {
ParentClass.myList.add(new CustomClass("asdfasdf.."));
ParentClass.myList.add(new CustomClass("fdsafdsa..."));
}
public int arraySize() {
return ParentClass.myList.size();
}
}
As you are again declaring and iniatilizing same array, so there possibility of getting lost the data. try this one out
Related
I have a User class. And two subclasses. Parent and Child.
I get json from my server with {"user":"..."} and need to convert it to parent or to child depending on user.type
As I understand I need to add custom converter this way:
Moshi moshi = new Moshi.Builder()
.add(new UserAdapter())
.build();
Here's my implementation of UserAdapter. I know it's dummy, but it's not working even this way:
public class UserAdapter {
#FromJson
User fromJson(String userJson) {
Moshi moshi = new Moshi.Builder().build();
try {
JSONObject jsonObject = new JSONObject(userJson);
String accountType = jsonObject.getString("type");
switch (accountType) {
case "Child":
JsonAdapter<Child> childJsonAdapter = moshi.adapter(Child.class);
return childJsonAdapter.fromJson(userJson);
case "Parent":
JsonAdapter<Parent> parentJsonAdapter = moshi.adapter(Parent.class);
return parentJsonAdapter.fromJson(userJson);
}
} catch (JSONException | IOException e) {
e.printStackTrace();
}
return null;
}
#ToJson
String toJson(User user) {
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<User> jsonAdapter = moshi.adapter(User.class);
String toJson = jsonAdapter.toJson(user);
return toJson;
}
First of all I get following exception with this code.
com.squareup.moshi.JsonDataException: Expected a string but was BEGIN_OBJECT at path $.user
And second, I believe there's a better way to do it. Please advice.
Upd. here's stacktrace for the error:
com.squareup.moshi.JsonDataException: Expected a name but was BEGIN_OBJECT at path $.user
at com.squareup.moshi.JsonReader.nextName(JsonReader.java:782)
at com.squareup.moshi.ClassJsonAdapter.fromJson(ClassJsonAdapter.java:141)
at com.squareup.moshi.JsonAdapter$1.fromJson(JsonAdapter.java:68)
at com.squareup.moshi.JsonAdapter.fromJson(JsonAdapter.java:33)
at retrofit.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:33)
at retrofit.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:23)
at retrofit.OkHttpCall.parseResponse(OkHttpCall.java:148)
at retrofit.OkHttpCall.execute(OkHttpCall.java:116)
at retrofit.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:111)
at retrofit.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:88)
at rx.Observable$2.call(Observable.java:162)
at rx.Observable$2.call(Observable.java:154)
at rx.Observable$2.call(Observable.java:162)
at rx.Observable$2.call(Observable.java:154)
at rx.Observable.unsafeSubscribe(Observable.java:7710)
at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
This seems to me like the example you want to follow for your custom de/serialization of your JSON data: https://github.com/square/moshi#another-example
It uses an intermediate class that corresponds to the JSON structure, and Moshi will inflate it automatically for you. Then, you can use the inflated data to build your specialized user classes. For example:
// Intermediate class with JSON structure
class UserJson {
// Common JSON fields
public String type;
public String name;
// Parent JSON fields
public String occupation;
public Long salary;
// Child JSON fields
public String favorite_toy;
public Integer grade;
}
abstract class User {
public String type;
public String name;
}
final class Parent extends User {
public String occupation;
public Long salary;
}
final class Child extends User {
public String favoriteToy;
public Integer grade;
}
Now, the adapter:
class UserAdapter {
// Note that you pass in a `UserJson` object here
#FromJson User fromJson(UserJson userJson) {
switch (userJson.type) {
case "Parent":
final Parent parent = new Parent();
parent.type = userJson.type;
parent.name = userJson.name;
parent.occupation = userJson.occupation;
parent.salary = userJson.salary;
return parent;
case "Child":
final Child child = new Child();
child.type = userJson.type;
child.name = userJson.name;
child.favoriteToy = userJson.favorite_toy;
child.grade = userJson.grade;
return child;
default:
return null;
}
}
// Note that you return a `UserJson` object here.
#ToJson UserJson toJson(User user) {
final UserJson json = new UserJson();
if (user instanceof Parent) {
json.type = "Parent";
json.occupation = ((Parent) user).occupation;
json.salary = ((Parent) user).salary;
} else {
json.type = "Child";
json.favorite_toy = ((Child) user).favoriteToy;
json.grade = ((Child) user).grade;
}
json.name = user.name;
return json;
}
}
I think that this is much cleaner, and allows Moshi to do its thing, which is creating objects from JSON and creating JSON from objects. No mucking around with old-fashioned JSONObject!
To test:
Child child = new Child();
child.type = "Child";
child.name = "Foo";
child.favoriteToy = "java";
child.grade = 2;
Moshi moshi = new Moshi.Builder().add(new UserAdapter()).build();
try {
// Serialize
JsonAdapter<User> adapter = moshi.adapter(User.class);
String json = adapter.toJson(child);
System.out.println(json);
// Output is: {"favorite_toy":"java","grade":2,"name":"Foo","type":"Child"}
// Deserialize
// Note the cast to `Child`, since this adapter returns `User` otherwise.
Child child2 = (Child) adapter.fromJson(json);
System.out.println(child2.name);
// Output is: Foo
} catch (IOException e) {
e.printStackTrace();
}
There's now a much better way to do this, using PolymorphicJsonAdapterFactory. See https://proandroiddev.com/moshi-polymorphic-adapter-is-d25deebbd7c5
You probably tried to implement you parsing according to: https://github.com/square/moshi#custom-type-adapters
There String is used as an argument of #FromJson method, so it can be magically parsed to some mapping helper class or String and we have to parse it manually, right? Actually no, you can either use mapping helper class or Map.
Thus your exception Expected a string but was BEGIN_OBJECT at path $.user was caused by Moshi trying to get that user as a String (because that's what you implied in your adapter), whereas it is just another object.
I don't like parsing ALL possible fields to some helper class as in case of polymorphism that class might become very big and you need to rely or remembering/commenting code.
You can handle it as a map - that is default model for unknown types - and convert it to json, so in your case that would look something like:
#FromJson
User fromJson(Map<String, String> map) {
Moshi moshi = new Moshi.Builder().build();
String userJson = moshi.adapter(Map.class).toJson(map);
try {
JSONObject jsonObject = new JSONObject(userJson);
String accountType = jsonObject.getString("type");
switch (accountType) {
case "Child":
JsonAdapter<Child> childJsonAdapter = moshi.adapter(Child.class);
return childJsonAdapter.fromJson(userJson);
case "Parent":
JsonAdapter<Parent> parentJsonAdapter = moshi.adapter(Parent.class);
return parentJsonAdapter.fromJson(userJson);
}
} catch (JSONException | IOException e) {
e.printStackTrace();
}
return null;
}
Of course you can just handle map directly: retrieve "type" string and then parse the rest of map to chosen class. Then there is no need to use JSONObject at all with nice benefit of not being dependent on Android and easier testing of parsing.
#FromJson
User fromJson(Map<String, String> map) {
Moshi moshi = new Moshi.Builder().build();
try {
String userJson = moshi.adapter(Map.class).toJson(map);
switch (map.get("type")) {
case "Child":
JsonAdapter<Child> childJsonAdapter = moshi.adapter(Child.class);
return childJsonAdapter.fromJson(userJson);
case "Parent":
JsonAdapter<Parent> parentJsonAdapter = moshi.adapter(Parent.class);
return parentJsonAdapter.fromJson(userJson);
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
I am new to the world of Generics and am trying to write a utility class that will take a list of Objects and persist it to the store and then retrieve it back.
This is what I wrote to save the list:
public static void saveListToStore(Context ctx, String fileName, list<Object> listToStore) throws IOException
{
String elemValue = "";
Gson gson = new Gson();
try {
FileOutputStream fileOutputStream = ctx.openFileOutput(fileName, ctx.MODE_PRIVATE);
elemValue= gson.toJson(listToStore);
fileOutputStream.write(elemValue.getBytes());
objectOutputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
However when I try to retrieve, I will not be aware of the type of object that was there in the list and cannot rebuild it back. I do not want to put type comparisons as I would like to save any type of custom class and the list can be huge.
I want to deduce the type from the content itself. I was thinking of saving the type as the first line and then the data. So on retrieve I can get the type first and then typecast the objects. However is there any other cleaner way of achieving this ?
Ur Object should implement Serializable and the below code can help you to read and write
public static void readListToStore(Context ctx, String fileName, List<Object> listToStore) throws IOException {
SharedPreferences storeDataPref = ctx.getSharedPreferences("UR_KEY", Context.MODE_PRIVATE);
String elemValue = storeDataPref.getString("UR_NAME", null);
if (elemValue != null) {
Type listType = new TypeToken<ArrayList<Object>>() {
}.getType();
listToStore = new Gson().fromJson(elemValue, listType);
}
}
public static void saveListToStore(Context ctx, String fileName, List<Object> listToStore) throws IOException {
String elemValue = "";
Gson gson = new Gson();
try {
FileOutputStream fileOutputStream = ctx.openFileOutput(fileName, ctx.MODE_PRIVATE);
elemValue = gson.toJson(listToStore);
SharedPreferences storeDataPref = ctx.getSharedPreferences("UR_KEY", Context.MODE_PRIVATE);
Editor storeDataEditor = storeDataPref.edit();
storeDataEditor.clear();
storeDataEditor.putString("UR_NAME", elemValue).apply();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
Taken from this question: Trouble with Gson serializing an ArrayList of POJO's
"You need to give Gson information on the specific generic type of List you're using (or any generic type you use with it). Particularly when deserializing JSON, it needs that information to be able to determine what type of object it should deserialize each array element to.
Type listOfTestObject = new TypeToken<List<TestObject>>(){}.getType();
String s = gson.toJson(list, listOfTestObject);
List<TestObject> list2 = gson.fromJson(s, listOfTestObject);
This is documented in the Gson user guide."
You can write and read the string to a file. The List can be of any collection type implementing the List interface
I was working on capturing the order of elements contained in tag. Here is all the code:
League.java:
#Root
#Convert(value = LeagueConverter.class)
public class League
{
#Attribute
private String name;
#Element(name="headlines", required = false)
private Headlines headlines;
#Element(name="scores", required = false)
private Scores scores;
#Element(name="standings", required = false)
private Standing standings;
#Element(name="statistics", required = false)
private LeagueStatistics statistics;
public List<String> order = new ArrayList<String>();
// get methods for all variables
}
LeagueConverter.java:
public class LeagueConverter implements Converter<League>
{
#Override
public League read(InputNode node) throws Exception
{
League league = new League();
InputNode next = node.getNext();
while( next != null )
{
String tag = next.getName();
if(tag.equalsIgnoreCase("headlines"))
{
league.order.add("headlines");
}
else if(tag.equalsIgnoreCase("scores"))
{
league.order.add("scores");
}
else if(tag.equalsIgnoreCase("statistics"))
{
league.order.add("statistics");
}
else if(tag.equalsIgnoreCase("standings"))
{
league.order.add("standings");
}
next = node.getNext();
}
return league;
}
#Override
public void write(OutputNode arg0, League arg1) throws Exception
{
throw new UnsupportedOperationException("Not supported yet.");
}
}
Exampe of XML:
<android>
<leagues>
<league name ="A">
<Headlines></Headlines>
<Scores></Scores>
...
</league>
<league name ="B">...</league>
</leagues>
</android>
How I'm calling it and expecting it to behave: (Snippet)
Android android = null;
Serializer serial = new Persister(new AnnotationStrategy());
android = serial.read(Android.class, source);
Log.i("Number of leagues found ",tsnAndroid.getLeagueCount() + ""); // prints fine
League nhl = tsnAndroid.getLeagues().get(0); // works fine
// DOES NOT WORK throws NullPointerEx
League nhl2 = tsnAndroid.getLeagueByName("A");
// DOES NOT WORK throws NullPointerEx
for(String s : nhl.getOrder())
{
Log.i("ORDER>>>>>", s);
}
The problem:
android.getLeagueByName() (Works with #Attribute name) suddenly stops working when I have the converter set, so its like the following from League.java, never gets set.
#Attribute
private String name; // not being set
However, when I comment out the converter declaration in League.java - Every league has an attribute called name and android.getLeagueByName() starts working fine...
Does #Convert for League somehow interfere with #Attribute in League?
Even though this question is outrageously old (as is the SimpleXML library), I will give my two cents.
#Convert annotation works only with #Element, but it does not have any effect on #Attribute. I'm not sure if that's a bug or a feature, but there is another way of handling custom serialized objects - called Transform with Matcher, and it works both with Attributes and with Elements. Instead of using the Converters, you define a Transform class that handles serialization and deserialization:
import java.util.UUID;
import org.simpleframework.xml.transform.Transform;
public class UUIDTransform implements Transform<UUID> {
#Override
public UUID read(String value) throws Exception {
return value != null ? UUID.fromString(value) : null;
}
#Override
public String write(UUID value) throws Exception {
return value != null ? value.toString() : null;
}
}
As you can see, it is more straight-forward than implementing the Convert interface!
Create a similar class for all your objects that require custom de/serialization.
Now instantiate a RegistryMatcher object and register there your custom classes with their corresponding Transform classes. This is a thread-safe object that internally uses a cache, so it might be a good idea to keep it as a singleton.
private static final RegistryMatcher REGISTRY_MATCHER = new RegistryMatcher();
static {
try {
REGISTRY_MATCHER.bind(UUID.class, UUIDTransform.class);
// register all your Transform classes here...
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Finally, you can create a Persister class each time before a conversion and pass it the AnnotationStrategy together with your RegistryMatcher instance. In this factory method below, we will also use an indenting formatter:
private static Persister createPersister(int indent) {
return new Persister(new AnnotationStrategy(), REGISTRY_MATCHER, new Format(indent));
}
Now you can make your serialization/deserialization methods:
public static String objectToXml(Object object, int indent) throws MyObjectConversionException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Persister p = createPersister(indent);
try {
p.write(object, out, "UTF-8");
return out.toString("UTF-8");
} catch (Exception e) {
throw new MyObjectConversionException("Cannot serialize object " + object + " to XML: " + e.getMessage(), e);
}
}
public static <T> T xmlToObject(String xml, final Class<T> clazz) throws MyObjectConversionException {
Persister p = createPersister(0);
try {
return (T) p.read(clazz, xml);
} catch (Exception e) {
throw new MyObjectConversionException(
"Cannot deserialize XML to object of type " + clazz + ": " + e.getMessage(), e);
}
}
The only issue with this approach is when you want to have different formatting for the same object - e.g. once you want the java.util.Date to have just the date component, while later on you also want to have the time component. Then just extend the Date class, calling it DateWithTime, and make a different Transform for it.
#ElementListUnion will capture the order of elements
The #Convert annotation works only on #Element fields. I am struggling against converting #Attribute fields too but with no success for now...
I want to serialize a custom Java object, so I can use SharedPreferences to store it and retreive it in another Activity. I don't need persistant storage, the SharedPreferences, I wipe them when my application is closed. I'm currently using GSON for this, but it doesn't seem to work well with Android's SparseArray type.
My objects:
public class PartProfile {
private int gameId;
// Some more primitives
private SparseArray<Part> installedParts = new SparseArray<Part>();
// ...
}
public class Part {
private String partName;
// More primitives
}
Serialization:
Type genericType = new TypeToken<PartProfile>() {}.getType();
String serializedProfile = Helpers.serializeWithJSON(installedParts, genericType);
preferences.edit().putString("Parts", serializedProfile).commit();
serializeWithJSON():
public static String serializeWithJSON(Object o, Type genericType) {
Gson gson = new Gson();
return gson.toJson(o, genericType);
}
Deserialization:
Type genericType = new TypeToken<PartProfile>() {}.getType();
PartProfile parts = gson.fromJson(preferences.getString("Parts", "PARTS_ERROR"), genericType);
SparseArray<Part> retreivedParts = parts.getInstalledParts();
int key;
for (int i = 0; i < retreivedParts.size(); i++) {
key = retreivedParts.keyAt(i);
// Exception here:
Part part = retreivedParts.get(key);
// ...
}
Exception:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.mypackage.objects.Part
I don't understand why Gson wants to cast a LinkedTreeMap to my object, I never use one in my entire program. I used to have a HashMap<Integer,Part> before I switched to the SparseArray<Part>, and never had issues with that. Are SparseArrays not supported by Gson, or is there an error on my side?
Edit: It seems that the SparseArray gets deserialized correctly, but not the objects inside. Instead of LinkedTreeMaps, these should be of type Part.
Really there is a way to serialize any kind of SparseArray, here is an example code:
public class SparseArrayTypeAdapter<T> extends TypeAdapter<SparseArray<T>> {
private final Gson gson = new Gson();
private final Class<T> classOfT;
private final Type typeOfSparseArrayOfT = new TypeToken<SparseArray<T>>() {}.getType();
private final Type typeOfSparseArrayOfObject = new TypeToken<SparseArray<Object>>() {}.getType();
public SparseArrayTypeAdapter(Class<T> classOfT) {
this.classOfT = classOfT;
}
#Override
public void write(JsonWriter jsonWriter, SparseArray<T> tSparseArray) throws IOException {
if (tSparseArray == null) {
jsonWriter.nullValue();
return;
}
gson.toJson(gson.toJsonTree(tSparseArray, typeOfSparseArrayOfT), jsonWriter);
}
#Override
public SparseArray<T> read(JsonReader jsonReader) throws IOException {
if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.nextNull();
return null;
}
SparseArray<Object> temp = gson.fromJson(jsonReader, typeOfSparseArrayOfObject);
SparseArray<T> result = new SparseArray<T>(temp.size());
int key;
JsonElement tElement;
for (int i = 0; i < temp.size(); i++) {
key = temp.keyAt(i);
tElement = gson.toJsonTree(temp.get(key));
result.put(key, gson.fromJson(tElement, classOfT));
}
return result;
}
}
and to use it you need to register it in your Gson object, like this:
Type sparseArrayType = new TypeToken<SparseArray<MyCustomClass>>() {}.getType();
Gson gson = new GsonBuilder()
.registerTypeAdapter(sparseArrayType, new SparseArrayTypeAdapter<MyCustomClass>(MyCustomClass.class))
.create();
you can find this example in this gist.
P.S.: I know it's not optimized at all, but it's only an example to give an idea on how to achieve what you need.
It seems that the SparseArray gets deserialized correctly, but not the
objects inside. Instead of LinkedTreeMaps, these should be of type
Part.
Your observation is correct, since SparseArray contains Object (not Part), Gson won't have any clue to make Part as your object type. Hence it map your list as its infamous internal type LinkedTreeMap.
To solve it, I think you won't be able to use SparseArray... Or you may try retreivedParts.get(key).toString(), then use gson to parse the object again. But I don't think it's efficient to do that
As pointed out in the other answers SparseArray's internal implementation uses an Object[] to store the values so Gson cannot deserialize it correctly.
This can be solved by creating a custom Gson TypeAdapterFactory:
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import android.util.SparseArray;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
public class SparseArrayTypeAdapterFactory implements TypeAdapterFactory {
public static final SparseArrayTypeAdapterFactory INSTANCE = new SparseArrayTypeAdapterFactory();
private SparseArrayTypeAdapterFactory() { }
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// This factory only supports (de-)serializing SparseArray
if (type.getRawType() != SparseArray.class) {
return null;
}
// Get the type argument for the element type parameter `<E>`
// Note: Does not support raw SparseArray type (i.e. without type argument)
Type elementType = ((ParameterizedType) type.getType()).getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
// This is safe because check at the beginning made sure type is SparseArray
#SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new SparseArrayTypeAdapter<>(elementAdapter);
// call nullSafe() to make adapter automatically handle `null` SparseArrays
return adapter.nullSafe();
}
private static class SparseArrayTypeAdapter<E> extends TypeAdapter<SparseArray<E>> {
private final TypeAdapter<E> elementTypeAdapter;
public SparseArrayTypeAdapter(TypeAdapter<E> elementTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
}
#Override
public void write(JsonWriter out, SparseArray<E> sparseArray) throws IOException {
out.beginObject();
int size = sparseArray.size();
for (int i = 0; i < size; i++) {
out.name(Integer.toString(sparseArray.keyAt(i)));
elementTypeAdapter.write(out, sparseArray.valueAt(i));
}
out.endObject();
}
#Override
public SparseArray<E> read(JsonReader in) throws IOException {
in.beginObject();
SparseArray<E> sparseArray = new SparseArray<>();
while (in.hasNext()) {
int key = Integer.parseInt(in.nextName());
E value = elementTypeAdapter.read(in);
// Use `append(...)` here because SparseArray is serialized in ascending
// key order so `key` will be > previously added key
sparseArray.append(key, value);
}
in.endObject();
return sparseArray;
}
}
}
This factory serializes SparseArrays as JSON objects with the key as JSON property name and the value serialized with the respective adapter as JSON value, e.g.:
new SparseArray<List<String>>().put(5, Arrays.asList("Hello", "World"))
↓ JSON
{"5": ["Hello", "World"]}
You then use this TypeAdapterFactory by creating your Gson instance using a GsonBuilder on which you register the TypeAdapterFactory:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(SparseArrayTypeAdapterFactory.INSTANCE)
.create();
I'm a noob when it comes to basically all forms of storage aside from SharedPreferences and some SQLite. I did some searching and found that JSON+GSON was a fast way to parse Objects and their fields into storable Strings.
So, in my game, I have a Player object which has fields that are also my own classes:
public class Player {
private int something_game_related = 1;
private Skill equipped_skill;
private Item equipped_weapon;
public Player () {}
}
I suspect those classes are the problem, because when I try to run a simple save method:
private class ItemSerializer implements JsonSerializer<Item> {
public JsonElement serialize( Item src, Type typeOfSrc, JsonSerializationContext context ) {
return new JsonPrimitive(src.toString());
}
}
private class SkillSerializer implements JsonSerializer<Skill> {
public JsonElement serialize( Skill src, Type typeOfSrc, JsonSerializationContext context ) {
return new JsonPrimitive(src.toString());
}
}
public void doSave() {
GsonBuilder gson = new GsonBuilder();
//Both custom classes have zero-arg constructors so we don't need to register those
gson.registerTypeAdapter( Item.class, new ItemSerializer() );
gson.registerTypeAdapter( Skill.class, new SkillSerializer() );
Gson g = gson.create();
String mPlayer = "";
Type player = new TypeToken<Player>(){}.getType();
try{
mPlayer = g.toJson( GameView.mPlayer, player );
}
catch (Exception e) {e.printStackTrace();}
}
I get this Exception: java.lang.IllegalStateException: How can the type variable not be present in the class declaration!
My Question is..
How do I get these custom serializers to work? Like I said, I'm a noob.. but it looks like I did it right..
In the docs it says (kind of in the fine print) that static fields are excluded: http://sites.google.com/site/gson/gson-user-guide#TOC-Excluding-Fields-From-Serialization
You can do something like "excludeFieldsWithModifier(Modifier.STATIC)" in the GSON builder to include them.