1
votes

I am using a ActiveAndroid to cache response from Retrofit network calls.

I made a class which looks like :-

class Article extends Model {
    @Column(name = "title")
    @SerializedName("title")
    @Expose
    public String title;

    @Column(name = "authors")
    @SerializedName("authors")
    @Expose
    private String authors;
}

The corresponding JSON is something like :-

{
  "title":"Some title",
  "authors": [{
    "name": "Name",
    "twitter":"@whatever"
  }]
}

The sane way would be to use a has many relationship between Author and Article class, but authors are not being saved to database because they don't have @Expose annotation (or even a variable) in the Article class, and there is no way to have that annotation because has many calls will be a method call here which returns a List<Author>.

// This will not work
@Expose
public List<Author> authors() {
    return getMany(Author.class, "Article");
}

So the other way I thought was to store the JSON object in database as a string and serialize/deserialize for access.

That requires me to have the authors variable a List<Author> class, and provide a type serializer for it.

The problem with this approach is I have no way of knowing the Generic type being saved into the database. I can't return a List<Author> type because just like authors, there are some more JSON Arrays in the whole of JSON.

So my the final solution that I have thought of is to store it as a string, treat it as a string and make a method to deserialize for access. Like this :-

private List<Author> _authors;
public List<Author> authors() {
    if (_authors == null) {
        Gson gson = ServiceGenerator.gsonBuilder.create();
        Type type = new TypeToken<List<Author>>(){}.getType();
        _authors = gson.fromJson(authors, type);
    }

    return _authors;
}

But when I try to do this, GSON throws an error :-

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_ARRAY

Basically saying that I am trying to treat a JSON array as a String.

So my question is how to treat some of the JSON Arrays as Strings with GSON converters.

My Retrofit Builder which I am using is :-

public static final GsonBuilder gsonBuilder = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
            .excludeFieldsWithoutExposeAnnotation();

private static final Retrofit.Builder builder = new Retrofit.Builder()
        .baseUrl(API_BASE_URL)
        .addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()));

Thanks for taking a look!

5
what do you mean by and there is no way to have that annotation because has many calls will be a method call here which returns a List<Author> ?Blackbelt
@Blackbelt updated my question to explain it.Shobhit
I am not quite sure yet what do you mean. Why can't article hold private List<Author> authors; ?Blackbelt
Article can hold the method, it's just that Gson doesn't play with methods for deserializing JSON.Shobhit
if you replace private String authors; with private List<Author> authors, and Author is correctly declared to reflect the content of your json, there is no reason why Gson shouldn't be able to parse itBlackbelt

5 Answers

1
votes

If someone still wants to leave nested object as a string then you can use @JsonAdapter annotation for the field that should stay a string.

@JsonAdapter(RawStringTypeAdapter.class)
private String Authors;

and RawStringTypeAdapter:

public final class RawStringTypeAdapter<T>
        implements JsonDeserializer<T> {

   
    private RawStringTypeAdapter() {
    }

    @Override
    public T deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
            throws JsonParseException {
        return (T) jsonElement.toString();
    }

}
0
votes

you could use a JsonDeserializer for it, registered on Article

 public class ArticleDeserializer implements JsonDeserializer<Article> {


  @Override
  public Article deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
      throws JsonParseException {

     Article article = context.deserialize(json, Article.class);
     JsonArray authors = json.getAsJsonObject().getAsJsonArray("authors");
     // convert authors as string and set it to artile
    return article;
}

and you will have to register it to gson

 gsonBuilder.registerTypeHierarchyAdapter(Article.class, new ArticleDeseriazer())
0
votes

What Blackbelt said, for storing as String.

The sane way to do it would be to have a Author class and then a 1:n. As far as I understand, writing releationship data is manual, so you have to save the relationship yourself when you save the Article. So assuming you have the Author class

@Table(name = "Authors")
public class Author extends Model {
    @Column(name = "Name")
    public String name;
    @Column(name = "Twitter")
    public String twitter;
}

and the field

private List<Author> authors;

in the Article class, you would save the authors like this

article.save();
for (author: Author in arcticle.authors) {
    author.article = article;
    author.save();
}

and then you could add the method for accessing an Article's authors as documented:

public List<Author> getAuthors() {
    return getMany(Author.class, "Author");
}

So GSON stores the authors in the field authors, when saving the Authors get insterted to their own table, and getAuthors() reads them from the database.

note: m:n probably makes more sense as a relationship between Article and Author, which is not supported by ActiveAndroid and needs a junction table, as described in this issue on github

0
votes

one possible solution.

  1. Just declare field as Object, then Gson will deserialize it as LinkedTreeMap

  2. Try convert LinkedTreeMap to JsonObject as below

    LinkedTreeMap<?,?> yourMap = ...; JsonObject jsonObject = gson.toJsonTree(yourMap).getAsJsonObject();

0
votes

Use custom JsonDeserializer<Article> for overriding standard operation. Call toString() on your key authors

public class CustomDeserializer implements JsonDeserializer<Article> {
    @Override
    public Article deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {      
        if(json.isJsonObject()) {
            JsonObject jsonObj = json.getAsJsonObject();
            Article item = context.deserialize(json, Article.class);
            if(jsonObj.has("authors")) {
                item.setAuthors(jsonObj.get("authors").toString());
            }
            return item;
        }
        return null;
    }
}

And register custom deserializer

Gson gson = new GsonBuilder().registerTypeAdapter(Article.class, new CustomDeserializer()).create();

And don't forget to exclude key authors from gson deserialisation using @Expose annotation or addDeserializationExclusionStrategy()