Moshi自定义适配器用于通用List<T:Enum>,返回的是List<List<T:Enum>>而不是List<T:Enum>。

3
(还在这里打开了一个问题:https://github.com/square/moshi/issues/768,但被要求也提出一个stackoverflow的问题) 我正在编写一个通用适配器,将带有枚举值列表的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个回答

7

注册您的适配器,处理List<Tags>而不是Tags

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

此外,在您的解码实现中,您需要添加jsonReader.beingArray()和jsonReader.endArray()调用。(请注意,对于直接扩展JsonAdapter,您不需要使用@FromJson。)
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
}

奖励:您可以使用JsonReader.Options优化SkipNotFoundEnumInEnumListAdapter。

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()
  }
}

非常感谢,注册适配器是问题所在。我之前在代码中调用了 beginArray()endArray() 函数,但发现由于错误的注册,beginArray() 已经被调用了。 - CitrusO2
有没有其他的方式来编写上面的代码,而避免使用展开运算符? - oznecro
@eric-cochran 我在想,对于所有的List<Enum>,Moshi是否应该将此设置为默认行为,或者至少允许通过Moshi Builder上的某个标志来设置,而不是使用适配器。我已经在Github上创建了一个问题来讨论这个问题: https://github.com/square/moshi/issues/1118 - rewgoes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接