使用 Ktor 时出现“未找到转换:类 io.ktor.utils.io.ByteChannelNative”错误

Gar*_*ler 16 json kotlin ktor kotlin-multiplatform

我正在尝试获取并反序列化github上托管的一些数据。

{
  "Meals": [
    {
      "id": "1598044e-5259-11e9-8647-d663bd870b02",
      "name": "Tomato pasta",
      "quantity": [{
        "quantity": 1 },
        {
          "quantity": 2
        },
        {
          "quantity": 3
        }],
      "availableFromDate": "1605802429",
      "expiryDate": "1905802429",
      "info": "Vegetarian",
      "hot": false,
      "locationLat": 57.508865,
      "locationLong": -6.292,
      "distance": null
    },
    {
      "id": "2be2d854-a067-43ec-a488-2e69f0f2a624",
      "name": "Pizza",
      "quantity": [{
        "quantity": 1 },
        {
        "quantity": 2
        },
        {
          "quantity": 3
        }
      ],
      "availableFromDate": "1605802429",
      "expiryDate": "1905902429",
      "info": "Meat",
      "hot": false,
      "locationLat": 51.509465,
      "locationLong": -0.135392,
      "distance": null
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

如果我在本地启动json 服务器,那么它会完美工作,所以我知道我的数据类不是问题。但是,当我尝试从 github 链接执行相同的操作时,我收到此错误:

Error Domain=KotlinException Code=0 "No transformation found: class io.ktor.utils.io.ByteChannelNative -> class kotlin.collections.List

我有一种感觉,这可能与设置 ContentType 或类似的内容有关,但到目前为止我还没有成功地指定这一点。

这是我发出请求的代码:

class MealApi {

    private val httpClient = HttpClient {
        install(JsonFeature) {
            val json = Json { ignoreUnknownKeys = true }
            serializer = KotlinxSerializer(json)
        }
    }

    suspend fun getAllMeals(): List<Meal> {
        return httpClient.get(endpoint)
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的数据类,只是为了完整性:

@Serializable
data class Meal(
    @SerialName("id")
    val id: String,
    @SerialName("name")
    val name: String,
    @SerialName("quantity")
    val quantity: List<Quantity>,
    @SerialName("availableFromDate")
    var availableFromDate: String,
    @SerialName("expiryDate")
    var expiryDate: String,
    @SerialName("info")
    val info: String,
    @SerialName("hot")
    val hot: Boolean,
    @SerialName("locationLat")
    val locationLat: Float,
    @SerialName("locationLong")
    val locationLong: Float,
    @SerialName("distance")
    var distance: Double? = null
)

@Serializable
data class Quantity(
    @SerialName("quantity")
    val quantity: Int
)
Run Code Online (Sandbox Code Playgroud)

更新

我发现这个服务器https://gitcdn.link/允许您使用正确的 Content-Type 提供原始 github 文件。

sha*_*eep 16

我已经搜索了很多如何更改服务器响应标头(将其更改plain/textapplication/json),但似乎 ktor 实际上不允许这样做:

一个好的方法应该是允许ResponseObserver更改服务器响应标头并传递修改响应。但实际上你不能。

正如您所指出的,您的问题取决于原始 github 页面提供 headerContent-Type=plain/text而不是ContentType=application/json.

因此,当您在真实服务器中运行 API 时,IRL 不会发生这种情况,因为您会注意在服务器级别放置正确的内容类型。

但如果你想要解决这个问题,你可以用这种方式重写你的 api 调用:

suspend fun getAllMealsWithFallback() {
    var meals: Meals? = null
    try {
        meals = httpClient.get(endpoint)
    } catch (e: NoTransformationFoundException) {
        val mealsString: String = httpClient.get(endpoint)
        val json = kotlinx.serialization.json.Json {
            ignoreUnknownKeys = true
        }
        meals = json.decodeFromString(mealsString)
    } finally {
        println("Meals: ${meals?.meals}")
    }
}
Run Code Online (Sandbox Code Playgroud)

我必须添加此类以符合您在 github 链接中提供的 json 文本。

@Serializable
data class Meals(
    @SerialName("Meals")
    val meals: List<Meal>,
)
Run Code Online (Sandbox Code Playgroud)


小智 8

尝试这个:

install(JsonFeature) {
    serializer = KotlinxSerializer(KotlinJson { ignoreUnknownKeys = true })
    acceptContentTypes = acceptContentTypes + ContentType.Any
}
Run Code Online (Sandbox Code Playgroud)

如果您想接受所有内容类型。或者如果您愿意ContentType.Text.Any,可以使用 。ContentType.Text.Html


小智 5

终于得到了该堆栈用户 @ Aleksei_Tirman提到的正确的工作解决方案。

正如用户提到的,我们可以为特定内容类型(此处为 Content-Type:text/html)注册反序列化器来解决问题。

install(ContentNegotiation) {
// ...
register(
    ContentType.Text.Html, KotlinxSerializationConverter(
        Json {
            prettyPrint = true
            isLenient = true
            ignoreUnknownKeys = true
        } 
     ) 
  ) 
}
Run Code Online (Sandbox Code Playgroud)