Moshi: Choose adapter based on field value

Created on 17 Jul 2017  路  3Comments  路  Source: square/moshi

Hi!

I'm stuck with a seemingly simple problem. I want to choose adapter based on some field value (let's call it type) to construct types from class hierarchy. Obvious approach is to write some delegating adapter like this:

public class BaseTypeAdapter {

    @NonNull
    private final JsonAdapter<Derived1> derivedTypeAdapter1;

    @NonNull
    private final JsonAdapter<Derived2> derivedTypeAdapter2;

    @NonNull
    private final JsonAdapter<Map> mapAdapter;

    public BaseTypeAdapter(@NonNull Moshi subMoshi) {
        this.derivedTypeAdapter1 = subMoshi.adapter(Derived1.class);
        this.derivedTypeAdapter2 = subMoshi.adapter(Derived2.class);
        this.mapAdapter = subMoshi.adapter(Map.class);
    }

    @ToJson
    public String toJson(Base base) {
        ...
    }

    @FromJson
    public Base fromJson(Map<String, Object> map) {
        try {
            // FIXME: Redundantly transforming json from one form to another.
            // FIXME: Moshi's API doesn't allow to accept json as string directly.
            JSONObject jsonObject = new JSONObject(map);
            String json = mapAdapter.toJson(map);

            String type = jsonObject.getString("type");
            if (type != null) {
                switch (type) {
                    case "derived1":
                        return derivedTypeAdapter1.fromJson(json);
                    case "derived2":
                        return derivedTypeAdapter2.fromJson(json);
                }
            }
        } catch (JSONException | IOException e) {
            throw new RuntimeException(e);
        }

        return null;
    }
}

In comments you can see that I'm stuck with API limitations that force me to write such bizarre code. Is there any better way to do the same thing?

Most helpful comment

you could read the type off the map instead of converting to a string.
or

You could use the the JsonReader API directly. And, you can put in the delegating adapters as your method arguments to reuse the same Moshi instance.

@FromJson Base fromJson(JsonReader reader, JsonAdapter<Derived1> derivedTypeAdapter1,
    JsonAdapter<Derived2> derivedTypeAdapter2) throws IOException {
  Map<Object, String> value = (Map<Object, String>) reader.readJsonValue();
  String type = value.get("type");
  if (type == null) {
    return null;
  }
  switch (type) {
    case "derived1":
      return derivedTypeAdapter1.fromJsonValue(value);
    case "derived2":
      return derivedTypeAdapter2.fromJsonValue(value);
    default:
      return null;
  }
}

a couple examples: https://github.com/square/moshi/pull/264 and https://github.com/square/moshi/pull/256

I've been trying to write a blog post to demonstrate this better. hopefully soon.

All 3 comments

you could read the type off the map instead of converting to a string.
or

You could use the the JsonReader API directly. And, you can put in the delegating adapters as your method arguments to reuse the same Moshi instance.

@FromJson Base fromJson(JsonReader reader, JsonAdapter<Derived1> derivedTypeAdapter1,
    JsonAdapter<Derived2> derivedTypeAdapter2) throws IOException {
  Map<Object, String> value = (Map<Object, String>) reader.readJsonValue();
  String type = value.get("type");
  if (type == null) {
    return null;
  }
  switch (type) {
    case "derived1":
      return derivedTypeAdapter1.fromJsonValue(value);
    case "derived2":
      return derivedTypeAdapter2.fromJsonValue(value);
    default:
      return null;
  }
}

a couple examples: https://github.com/square/moshi/pull/264 and https://github.com/square/moshi/pull/256

I've been trying to write a blog post to demonstrate this better. hopefully soon.

you could probably copy-paste the mentioned RuntimeJsonAdapterFactory and use it for this, too, without writing all this code yourself.

Ah, I'm deeply sorry. I was using outdated Moshi version (1.3.0) and hadn't those methods in JsonReader yet I tried the approach with custom JsonAdapter. Thank you for answer and examples. Closing.

Was this page helpful?
0 / 5 - 0 ratings