26

I'm using retrofit with gson to deserialize my json into realm objects. This works very well for the most part. Trouble arises when dealing with

RealmList(String(or any other basic data type))

Since Realm doesnt support RealmList where E doesnt extend Realm object, I wrapped String in a RealmObject.

public class RealmString extends RealmObject {
  private String val;

  public String getValue() {
    return val;
  }

  public void setValue(String value) {
    this.val = value;
  }
}

My realm Object is as below

    public class RealmPerson extends RealmObject {
    @PrimaryKey
    private String userId;
    ...
    private RealmList<RealmString> stringStuff;
    private RealmList<SimpleRealmObj> otherStuff;

    <setters and getters>
   }

SimpleRealmObj works fine as it only has String elements

    public class SimpleRealmObj extends RealmObject {
    private String foo;
    private String bar;
       ...
    }

How can I deserialize stringStuff? I tried using a gson TypeAdapter

public class RealmPersonAdapter extends TypeAdapter<RealmPerson> {
    @Override
    public void write(JsonWriter out, RealmPerson value) throws IOException {
        out.beginObject();
        Log.e("DBG " + value.getLastName(), "");
        out.endObject();
    }

    @Override
    public RealmPerson read(JsonReader in) throws IOException {
        QLRealmPerson rList = new RealmPerson();
        in.beginObject();
        while (in.hasNext()) {
            Log.e("DBG " + in.nextString(), "");
        }
        in.endObject();

        return rList;
    }

However I still hit the IllegalStateException

2334-2334/com.qualcomm.qlearn.app E//PersonService.java:71﹕ main com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was NAME at line 1 column 3 path $.

I tried RealmList, RealmString adapter earlier to no avail. The only workaround I managed to find so far is https://github.com/realm/realm-java/issues/620#issuecomment-66640786 Any better options?

Vicky Chijwani
  • 10,191
  • 6
  • 56
  • 79
avid
  • 407
  • 1
  • 5
  • 11

4 Answers4

33

It is better to use JsonSerializer and JsonDeserializer rather than TypeAdapter for your RealmObject, because of 2 reasons:

  1. They allow you to delegate (de)serialization for your RealmObject to the default Gson (de)serializer, which means you don't need to write the boilerplate yourself.

  2. There's a weird bug in Gson 2.3.1 that might cause a StackOverflowError during deserialization (I tried the TypeAdapter approach myself and encountered this bug).

Here's how (replace Tag with your RealmObject class):

(NOTE that context.serialize and context.deserialize below are equivalent to gson.toJson and gson.fromJson, which means we don't need to parse the Tag class ourselves.)

Parser + serializer for RealmList<Tag>:

public class TagRealmListConverter implements JsonSerializer<RealmList<Tag>>,
        JsonDeserializer<RealmList<Tag>> {

    @Override
    public JsonElement serialize(RealmList<Tag> src, Type typeOfSrc,
                                 JsonSerializationContext context) {
        JsonArray ja = new JsonArray();
        for (Tag tag : src) {
            ja.add(context.serialize(tag));
        }
        return ja;
    }

    @Override
    public RealmList<Tag> deserialize(JsonElement json, Type typeOfT,
                                      JsonDeserializationContext context)
            throws JsonParseException {
        RealmList<Tag> tags = new RealmList<>();
        JsonArray ja = json.getAsJsonArray();
        for (JsonElement je : ja) {
            tags.add((Tag) context.deserialize(je, Tag.class));
        }
        return tags;
    }

}

Tag class:

@RealmClass
public class Tag extends RealmObject {
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

Then register your converter class with Gson:

Gson gson = new GsonBuilder()
        .registerTypeAdapter(new TypeToken<RealmList<Tag>>() {}.getType(),
                new TagRealmListConverter())
        .create();
Community
  • 1
  • 1
Vicky Chijwani
  • 10,191
  • 6
  • 56
  • 79
  • your solution is the best! – suyanlu May 04 '16 at 04:34
  • This solution works best and without the boilderplate code recommended in other solutions. – Arthur Jun 07 '16 at 11:32
  • 2
    this still doesn't work for me: `java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING` in the line `tags.add((RealmString) context.deserialize(je, RealmString.class));` – Henrique de Sousa Jun 07 '16 at 13:33
  • @HenriquedeSousa that error means the JSON you are receiving does not match your code in some way. Is your `RealmString` class defined in exactly the same way as in @avid's original question? Also what does your JSON look like? – Vicky Chijwani Jun 07 '16 at 13:37
  • @VickyChijwani I posted another question, if you fancy you can help me out there, thanks: http://stackoverflow.com/questions/37727315/error-deserializing-a-reallistrealmstring-into-liststring – Henrique de Sousa Jun 09 '16 at 13:25
  • Does this answer also assume a TypeAdapter for Tag has been registered ? Otherwise, how does GSON know how to serialize or deserialize the Tag class? – Espen Riskedal Sep 19 '16 at 12:22
  • It is not solving the problem. @VickyChijwani, Can you please show your `Tag` class as well? – Shajeel Afzal Nov 25 '16 at 12:56
  • @EspenRiskedal no, you don't need a TypeAdapter for `Tag`. Since GSON can automatically parse simple classes containing Strings, Integers, etc, we just take care of parsing the `RealmList` and let GSON parse the `Tag` itself (using `context.deserialize`) – Vicky Chijwani Nov 25 '16 at 15:05
  • @ShajeelAfzal my `Tag` class is just a simple class containing built-in Java types like `String`, `Integer`, etc. Since GSON already knows how to parse such classes, we don't need to deal with it at all. – Vicky Chijwani Nov 25 '16 at 15:07
  • To clarify further, my `Tag` class is equivalent to the `RealmString` and `SimpleRealmObj` classes defined in the original question above. – Vicky Chijwani Nov 25 '16 at 15:10
  • @VickyChijwani is it possible to use same class with dynamic type for all RealmLists? smth like > – Siarhei Jul 16 '17 at 18:55
  • @user599807 I'm not sure but looking at it quickly, I think it should be possible. – Vicky Chijwani Jul 17 '17 at 00:49
  • if there is 100 data class then there will be 100 converter class? is there any better solution? – zihadrizkyef Aug 31 '22 at 14:06
5

The error message "Expected a string but was NAME" can be solved by retrieving the name of the json object in the JsonReader before the actual json object (which is a String in your case).

You can take a look at the Android documentation for JsonReader. It has detailed explanation and code snippet. You can also take a look at the readMessage method in the sample code snippet in the documentation.

I have modified your read method to what I think it should be. NOTE: I didn't test the code, so there may be some minor errors in it.

@Override
public RealmPerson read(JsonReader in) throws IOException {
    RealmPerson rList = new RealmPerson();
    in.beginObject();
    String name = "";
    while (in.hasNext()) {
        name = in.nextName();

        if (name.equals("userId")) {
            String userId = in.nextString();
            // update rList here 
        } else if (name.equals("otherStuff")) {
            // since otherStuff is a RealmList of RealmStrings,
            // your json data would be an array
            // You would need to loop through the array to retrieve 
            // the json objects
            in.beginArray();
            while (in.hasNext()) {
                // begin each object in the array
                in.beginObject();
                name = in.nextName();
                // the RealmString object has just one property called "value"
                // (according to the code snippet in your question)
                if (name.equals("val")) {
                    String val = in.nextString();
                     // update rList here 
                } else {
                    in.skipValue();
                }
                in.endObject();
            }
            in.endArray();
        } else {
            in.skipValue();
        }
    }
    in.endObject();


    return rList;
}

Let me know if this helps.

iRuth
  • 2,737
  • 3
  • 27
  • 32
  • 1
    Thanks @iRuth. This is indeed the way to deserialize the json blob and answers the original question. I will mark this as the correct answer. For anyone else who only wish to deserialize the realmString, check my answer below. – avid Feb 26 '15 at 18:41
4

My gson typeAdapter was the culprit. The above error was seen as I wasnt deserializing the json into RealmPerson correctly, the first field is not a String, hence

in.nextString()

was borking.

I looked at some example code and it hit me, I didnt have to use

in.beginObject() and in.endObject()

to deserialize a String. The below code works.

public class QLRealmStringAdapter extends TypeAdapter<QLRealmString> {
@Override
public void write(JsonWriter out, QLRealmString value) throws IOException {
    Log.e("DBG " + value.getValue(), "");
    out.value(value.getValue());
}

@Override
public RealmString read(JsonReader in) throws IOException {
    RealmString rString = new RealmString();
    if (in.hasNext()) {
        String nextStr = in.nextString();
        System.out.println("DBG " + nextStr);
        rString.setValue(nextStr);
    }

    return rString;
}

}

Hope this helps someone.

avid
  • 407
  • 1
  • 5
  • 11
  • This was the missing link! I converted all my models to use primitive types and RealmString objects, setup an exclusions strategy like the one seen here: http://stackoverflow.com/questions/32434377/robospice-with-gson-and-realm/32891674#32891674 and then implemented this RealmString TypeAdapter and everything is working perfectly! Thanks – speedynomads Oct 14 '15 at 15:57
0

i need a jackson serializer and deserializer for the Converting Arraylist to RealmList

avez raj
  • 2,055
  • 2
  • 22
  • 37