Moshi Custom Adapter для универсального спискавозвращает список> вместо списка - PullRequest
0 голосов
/ 07 декабря 2018

(Здесь также открыта проблема: https://github.com/square/moshi/issues/768, но мне было предложено открыть также вопрос о переполнении стека) Я пишу универсальный адаптер для преобразования строки json со списками значений перечисления.Стандартный адаптер для перечислений генерирует исключение, когда список содержит значение перечисления, которое недоступно.Я хочу создать адаптер, который просто пропускает неизвестные значения перечисления, а не создает исключение.Мне это удалось частично, но по какой-то причине преобразованный объект был не List<Enum>, а List<List<Enum>>.

. Это адаптер, который я придумал:

package com.mytestcompany.appname.utils

import com.squareup.moshi.*
import kotlin.reflect.KClass

class SkipNotFoundEnumInEnumListAdapter<T : Enum<T>>(enumType: KClass<T>) : JsonAdapter<List<T>>(){
    val jsonNameToEnum = HashMap<String,T>()
    val enumToJsonName = HashMap<T,String>()

    init{
        val enumConstants =  enumType.java.enumConstants
        for(enumConstant in enumConstants){
            val constantName = enumConstant.name
            val jsonName = enumType.java.getField(constantName).getAnnotation(Json::class.java)

            val lookupName = jsonName?.name ?: constantName
            jsonNameToEnum[lookupName] = enumConstant
            enumToJsonName[enumConstant] = lookupName
        }
    }

    @FromJson
    override fun fromJson(jsonReader: JsonReader): List<T>{
        val list = ArrayList<T>()
        while(jsonReader.hasNext()){
            val jsonNameValue = jsonReader.nextString()
            val entry = jsonNameToEnum[jsonNameValue]
            if(entry!= null){
                list.add(entry)
            }
        }
        return list
    }

    @ToJson
    override fun toJson(writer: JsonWriter, list: List<T>?){
        if(list!=null){
            for(item in list){
                val jsonName = enumToJsonName[item]
                if(jsonName != null){
                    writer.value(jsonName)
                }
            }
        }
    }
}

и код модульного теста:

package com.mytestcompany.appname.utils

import com.squareup.moshi.Json
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner


data class TestJsonClass(
    val testJsonSubClass: TestJsonSubClass
)

data class TestJsonSubClass(
    val tags: List<Tags>
)

enum class Tags {
    @Json(name="tag1") TAG_1,
    @Json(name="tag2") TAG_2,
    @Json(name="tag3") TAG_3,
}

@RunWith(MockitoJUnitRunner::class)
class SkipNotFoundEnumInEnumListAdapterTest {

    lateinit var jsonAdapter: JsonAdapter<TestJsonClass>

    @Before
    fun setUp() {
        val moshi = Moshi.Builder()
            .add(Tags::class.java, SkipNotFoundEnumInEnumListAdapter(Tags::class))
            .add(KotlinJsonAdapterFactory())
            .build()
        jsonAdapter = moshi.adapter(TestJsonClass::class.java)
    }

    @Test
    fun moshiAdapterKnownEnumsTest() {
        val json = """
            {
                "testJsonSubClass": {
                    "tags": [
                        "tag1",
                        "tag2",
                        "tag3"
                    ]
                },
                "validation": {}
            }
        """.trimIndent()

        val testJsonClass = jsonAdapter.fromJson(json)
        Assert.assertTrue(testJsonClass?.testJsonSubClass?.tags?.count() == 3)
    }

    @Test
    fun moshiAdapterUnknownEnumsTest() {
        val json = """
            {
                "testJsonSubClass": {
                    "tags": [
                        "tag1",
                        "tag2",
                        "tag5"
                    ]
                },
                "validation": {}
            }
        """.trimIndent()

        val testJsonClass = jsonAdapter.fromJson(json)
        Assert.assertTrue(testJsonClass?.testJsonSubClass?.tags?.count() == 2)
    }
}

При отладке объекта testJsonClass второго теста я вижу следующие значения (также похожие для первого теста): image

Я думаю, что это связано с CollectionJsonAdapter, потому что пользовательский адаптер вызывается через CollectionJsonAdapter.Прежде, чем я думал, я бы передал коллекцию конвертеру и написал jsonReader.beginArray() и reader.endArray(), но это уже было сделано для меня:

   //in CollectionJsonAdapter.java 
  @Override public C fromJson(JsonReader reader) throws IOException {
    C result = newCollection();
    reader.beginArray();
    while (reader.hasNext()) {
      result.add(elementAdapter.fromJson(reader));    // calls the custom adapter
    }
    reader.endArray();
    return result;
  }

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

1 Ответ

0 голосов
/ 07 декабря 2018

Зарегистрируйте ваш адаптер для обработки List<Tags>, а не Tags.

.add(Types.newParameterizedType(List::class.java, Tags::class.java),
    SkipNotFoundEnumInEnumListAdapter(Tags::class))

Также вам необходимо добавить вызовы jsonReader.beingArray () и jsonReader.endArray () в вашу реализацию декодирования.(Обратите внимание, что вам не нужно @FromJson для прямого расширения JsonAdapter.)

override fun fromJson(jsonReader: JsonReader): List<T> {
  val list = ArrayList<T>()
  jsonReader.beginArray()
  while(jsonReader.hasNext()){
    val jsonNameValue = jsonReader.nextString()
    val entry = jsonNameToEnum[jsonNameValue]
    if(entry!= null){
      list.add(entry)
    }
  }
  jsonReader.endArray()
  return list
}

Бонус: вы можете оптимизировать SkipNotFoundEnumInEnumListAdapter с JsonReader.Options.

class SkipNotFoundEnumInEnumListAdapter<T : Enum<T>>(enumType: Class<T>) : JsonAdapter<List<T>>() {
  private val nameStrings: Array<String>
  private val constants: Array<T>
  private val options: JsonReader.Options

  init {
    try {
      constants = enumType.enumConstants
      nameStrings = Array(constants.size) {
        val constant = constants[it]
        val annotation = enumType.getField(constant.name).getAnnotation(Json::class.java)
        annotation?.name ?: constant.name
      }
      options = JsonReader.Options.of(*nameStrings)
    } catch (e: NoSuchFieldException) {
      throw AssertionError("Missing field in " + enumType.name, e)
    }

  }

  @Throws(IOException::class)
  override fun fromJson(reader: JsonReader): List<T> {
    reader.beginArray()
    val list = mutableListOf<T>()
    while (reader.hasNext()) {
      val index = reader.selectString(options)
      if (index != -1) {
        list += constants[index]
      } else {
        reader.skipValue()
      }
    }
    reader.endArray()
    return list
  }

  @Throws(IOException::class)
  override fun toJson(writer: JsonWriter, value: List<T>?) {
    if (value == null) {
      throw IllegalArgumentException("Wrap in .nullSafe()")
    }
    writer.beginArray()
    for (i in value.indices) {
      writer.value(nameStrings[value[i].ordinal])
    }
    writer.endArray()
  }
}
...