Как кодировать поле объекта как строковый JSON вместо вложенного объекта JSON в Moshi? - PullRequest
1 голос
/ 26 июня 2019

У меня есть запечатанный класс WebSocketMessage, у которого есть несколько подклассов. WebSocketMessage имеет поле с именем type, которое используется для различения подклассов.

Все подклассы имеют свое собственное поле с именем payload, которое имеет разный тип для каждого подкласса.

В настоящее время я использую PolymorphicJsonAdapterFactory от Moshi, чтобы эти классы можно было анализировать из JSON и кодировать в JSON.

Это все работает, но мне нужно закодировать поле payload в строковый JSON вместо объекта JSON.

Есть ли возможность написать собственный класс адаптера, чтобы помочь мне с этой проблемой? Или есть какое-то другое решение, чтобы мне не пришлось выполнять эту последовательность вручную?

Я пытался найти пользовательские адаптеры, но не могу найти, как передать экземпляр moshi в адаптер, чтобы я мог закодировать данное поле в JSON и затем его преобразовать в строку, а также я не нашел ничего другого, что могло бы мне помочь.

Класс WebSocketMessage с его подклассами:


sealed class WebSocketMessage(
    val type: Type
) {
    enum class Type(val type: String) {
        AUTH("AUTH"),
        PING("PING"),
        FLOW_INITIALIZATION("FLOW_INITIALIZATION")
    }

    class Ping : WebSocketMessage(Type.PING)
    class InitFlow(payload: InitFlowMessage) : WebSocketMessage(Type.FLOW_INITIALIZATION)
    class Auth(payload: Token) : WebSocketMessage(Type.AUTH)
}

Экземпляр Моши с PolymorphicJsonAdapterFactory:

val moshi = Moshi.Builder().add(
                    PolymorphicJsonAdapterFactory.of(WebSocketMessage::class.java, "type")
                        .withSubtype(WebSocketMessage.Ping::class.java, WebSocketMessage.Type.PING.type)
                        .withSubtype(
                            WebSocketMessage.InitFlow::class.java,
                            WebSocketMessage.Type.FLOW_INITIALIZATION.type
                        )                        
                        .withSubtype(WebSocketMessage.Auth::class.java, WebSocketMessage.Type.AUTH.type)
                )
                // Must be added last
                .add(KotlinJsonAdapterFactory())
                .build()

Как я кодирую в JSON:

moshi.adapter(WebSocketMessage::class.java).toJson(WebSocketMessage.Auth(fetchToken()))

В настоящее время я получаю JSON в следующем формате:

{  
   "type":"AUTH",
   "payload":{  
      "jwt":"some_token"
   }
}

Что бы я хотел получить:

{  
   "type":"AUTH",
   "payload":"{\"jwt\":\"some_token\"}"
}

Во втором примере полезная нагрузка - это строковый объект JSON, который как раз то, что мне нужно.

1 Ответ

1 голос
/ 27 июня 2019

Вы можете создать свой собственный JsonAdapter:

@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class AsString

/////////////////////

class AsStringAdapter<T>(
    private val originAdapter: JsonAdapter<T>,
    private val stringAdapter: JsonAdapter<String>
) : JsonAdapter<T>() {

    companion object {

        var FACTORY: JsonAdapter.Factory = object : Factory {
            override fun create(
                type: Type,
                annotations: MutableSet<out Annotation>,
                moshi: Moshi
            ): JsonAdapter<*>? {
                val nextAnnotations = Types.nextAnnotations(annotations, AsString::class.java)
                return if (nextAnnotations == null || !nextAnnotations.isEmpty())
                    null else {
                    AsStringAdapter(
                        moshi.nextAdapter<Any>(this, type, nextAnnotations),
                        moshi.nextAdapter<String>(this, String::class.java, Util.NO_ANNOTATIONS)
                    )
                }
            }
        }
    }

    override fun toJson(writer: JsonWriter, value: T?) {
        val jsonValue = originAdapter.toJsonValue(value)
        val jsonStr = JSONObject(jsonValue as Map<*, *>).toString()
        stringAdapter.toJson(writer, jsonStr)
    }

    override fun fromJson(reader: JsonReader): T? {
        throw UnsupportedOperationException()
    }
}

/////////////////////

class Auth(@AsString val payload: Token)

/////////////////////

.add(AsStringAdapter.FACTORY)
.add(KotlinJsonAdapterFactory())
.build()
...