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的自动转换适配器即可:
- 编写自定义类型适配器:
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) } }
- 初始化Moshi实例时注册该适配器:
val moshi = Moshi.Builder() .add(ByteArrayJsonAdapter()) .build()
注册后Moshi会自动处理序列化/反序列化时ByteArray和Base64字符串的转换,和后端默认规则完全对齐。
方案2:修改后端序列化配置
如果不想修改客户端,可以调整后端JSON序列化器的配置,将byte[]的输出格式改为数字数组。该方案会让相同二进制数据的传输体积比Base64大33%,非特殊场景不推荐使用。
内容的提问来源于stack exchange,提问作者Kanadista
相关产品推荐
相关产品推荐

