You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin使用Retrofit2结合Moshi时无法接收字节数组问题

问题描述

在Kotlin项目中使用Moshi编写用于收发字节数组的DTO类时,发送字节数组功能正常,接收数据抛出如下异常:

com.squareup.moshi.JsonDataException: Expected BEGIN_ARRAY but was STRING at path $[0].image

已确认后端API内存中处理的字段类型为字节数组,对应代码如下:

Kotlin端DTO定义

@JsonClass(generateAdapter = true)
data class LocationImageDTO (
    @Json(name="idLocationImage") val idLocationImage: Int,
    @Json(name = "idLocation") val idLocation: Int?,
    @Json(name="image") val image: ByteArray,
)

后端C#业务代码

// 接收查询逻辑
public List<clsLocationImage> getList(int idLocation)
{
    List<clsLocationImage> list = new List<clsLocationImage>();
    clsLocationImage locationImage;

    clsMyConnection connection = new clsMyConnection();

    SqlCommand command = new SqlCommand
    {
        CommandText = "SELECT idLocationImage, idLocation, image FROM K0_MAP_LOCATION_IMAGES WHERE idLocation = @idLocation",
        Connection = connection.getConnection()
    };

    command.Parameters.Add("@idLocation", System.Data.SqlDbType.Int).Value = idLocation;
    SqlDataReader reader;

    try
    {
        reader = command.ExecuteReader();
        if (reader.HasRows)
        {
            while (reader.Read())
            {
                locationImage = new clsLocationImage();
                locationImage.idLocationImage = (int)reader["idLocationImage"];
                locationImage.idLocation = (int)reader["idLocation"];
                locationImage.image = (byte[])reader["image"];
                list.Add(locationImage);
            }
        }
    }
    catch (SqlException excepcion)
    {
        throw excepcion;
    }
    return list;
}

public List<clsLocationImage> getListDAL(int id)
{
    return getList(id);
}


// 发送写入逻辑
public int createLocationImage(clsLocationImage locationImage)
{
    int filasAfectadas = 0;
    clsMyConnection conexion = new clsMyConnection();

    SqlCommand miComando = new SqlCommand
    {
        CommandText = "INSERT INTO K0_MAP_LOCATION_IMAGES(idLocation, image) VALUES (@idLocation, @image)",
        Connection = conexion.getConnection()
    };

    miComando.Parameters.Add("@idLocation", System.Data.SqlDbType.Int).Value = locationImage.idLocation;
    miComando.Parameters.Add("@image", System.Data.SqlDbType.VarBinary).Value = locationImage.image;

    try
    {
        filasAfectadas = miComando.ExecuteNonQuery();
    }
    catch (SqlException excepcion)
    {
        throw excepcion;
    }
    return filasAfectadas;
}
问题根因

异常核心原因是JSON格式本身不支持原生二进制类型,两端序列化规则不匹配:

  • C#后端默认的JSON序列化器(无论是System.Text.Json还是Newtonsoft.Json),会将内存中的byte[]类型编码为Base64格式字符串输出到JSON中,而非预期的、由0-255数字组成的JSON数组
  • Moshi内置的ByteArray适配器默认期望读取JSON数组格式(即[12,35,22,...]这种以[开头的结构),实际拿到的是"开头的字符串,因此抛出类型不匹配异常
  • 发送功能正常是因为:Moshi默认将ByteArray序列化为数字数组时,C#后端的反序列化器可以兼容识别该格式并转为byte[],双向规则不对等导致了单通问题。

你确认的“后端返回字节数组”只是服务端内存中的数据类型,不是最终JSON报文的传输格式,抓包查看接口返回即可发现image字段是Base64字符串而非数组。

解决方案

推荐优先使用方案1,符合JSON传输二进制数据的通用惯例,改动最小、传输效率更高。

方案1:客户端添加自定义Moshi适配器(推荐)

不需要修改后端代码,给Moshi添加Base64和ByteArray的自动转换适配器即可:

  1. 编写自定义类型适配器:
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
// 纯Java环境用java.util.Base64;Android环境替换为android.util.Base64,编解码参数传Base64.NO_WRAP
import java.util.Base64

class ByteArrayJsonAdapter {
    @ToJson
    fun toJson(data: ByteArray): String {
        return Base64.getEncoder().encodeToString(data)
    }

    @FromJson
    fun fromJson(base64Str: String): ByteArray {
        return Base64.getDecoder().decode(base64Str)
    }
}
  1. 初始化Moshi实例时注册该适配器:
val moshi = Moshi.Builder()
    .add(ByteArrayJsonAdapter())
    .build()

注册后Moshi会自动处理序列化/反序列化时ByteArray和Base64字符串的转换,和后端默认规则完全对齐。

方案2:修改后端序列化配置

如果不想修改客户端,可以调整后端JSON序列化器的配置,将byte[]的输出格式改为数字数组。该方案会让相同二进制数据的传输体积比Base64大33%,非特殊场景不推荐使用。


内容的提问来源于stack exchange,提问作者Kanadista

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 00:24:21