How do I deserialize a string within a moshi object as its own moshi object?

61 views Asked by At

I have a JSON object (that I can't change) that passes some data like so:

{
  "title": "foo",
  "actions": "[
    {
       \"label\": \"Hello\"
    }
  ]"
}

I'd like to deserialize the whole class with Moshi like so:

data class(
  val title: String,
  val actions: List<Action>
)

data class Action(
  val label: String
)

However, because the actions field is wrapped with quotes, Moshi has an issue trying to deserialize a string where it expects an array. Is there any way, such as through an adapter or something, to have it properly deserialize the actions string as if it were a json object?

1

There are 1 answers

1
der_Fidelis On

I ended up solving it with this system:

/**
 * A [JsonQualifier] that tells [Moshi] to unwrap a string before parsing it.
 * This is useful for when you have a string that is actually a json object.
 */
@JsonQualifier
annotation class JsonString

/**
 * A [JsonAdapter.Factory] that unwraps a string before parsing it.
 * This factory will apply to anything annotated with [JsonString]
 */
object JsonStringAdapterFactory: JsonAdapter.Factory {

  private class UnwrappingAdapter<T>(
      private val type: Type,
      private val moshi: Moshi
  ): JsonAdapter<T>() {
    override fun fromJson(reader: JsonReader): T? {
      val string = reader.nextString()                // Unwrap the string
      return moshi.adapter<T>(type).fromJson(string)  // Parse it as JSON
    }

    override fun toJson(writer: JsonWriter, value: T?) {
      throw UnsupportedOperationException()
    }
  }

  /**
   * Returns a [JsonAdapter] for `type` annotated with [JsonString], or null if `annotations` doesn't contain
   * [JsonString].
   */
  override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
    if (annotations.none { it is JsonString }) return null

    return UnwrappingAdapter<Any>(type, moshi)
  }
}

Then just annotate your property with @JsonString