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

Swift中如何将JSON转为Kotlin Multiplatform导出的对象实例?

KMM生成的iOS端DTO类不支持Decodable协议的解决方案

背景

开发架构采用Clean Architecture + MVVM:

  • Kotlin Multiplatform(KMM)公共层存放数据层代码(仓库、DTO类)
  • iOS原生端存放表现层、领域层代码(VC、ViewModel、UseCase)

KMM侧DTO代码

@Serializable
data class LoginResponse(val data: LoginResponseBody){
    @Serializable
    data class LoginResponseBody(
        var accessToken: String,
        var lastChannel: String? = null,
        var lastDateTime: String? = null,
        var userId: String? = null,
        var transactionalKeyStatus: String? = null,
        var accessStatus: String? = null
    )
}

KMM导出到iOS生成的Objective-C接口代码

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("LoginResponse")))
@interface SharedEmaLoginResponse : SharedEmaBase
- (instancetype)initWithData:(SharedEmaLoginResponseLoginResponseBody *)data __attribute__((swift_name("init(data:)"))) __attribute__((objc_designated_initializer));
- (SharedEmaLoginResponseLoginResponseBody *)component1 __attribute__((swift_name("component1()")));
- (SharedEmaLoginResponse *)doCopyData:(SharedEmaLoginResponseLoginResponseBody *)data __attribute__((swift_name("doCopy(data:)")));
- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
- (NSUInteger)hash __attribute__((swift_name("hash()")));
- (NSString *)description __attribute__((swift_name("description()")));
@property (readonly) SharedEmaLoginResponseLoginResponseBody *data __attribute__((swift_name("data")));
@end;

iOS侧测试代码

final class LoginRepositoryTest : XCTestCase, IAuthRepository {
var hasError = false

func login(loginRequest: LoginRequest, success: @escaping (LoginResponse) -> Void, failure: @escaping (ErrorResponse) -> Void, completionHandler: @escaping (KotlinUnit?, Error?) -> Void) {
    if hasError {
        failure(ErrorResponse(messages: []))
    } else {
        let data = loadStub(name: "loginResponse", extension: "json")
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .secondsSince1970
        
        let loginData = try! decoder.decode(LoginResponse.self, from: data)
        success(loginData)
    }
}

func getKoin() -> Koin_coreKoin {}
}

报错信息

Instance method 'decode(_:from:)' requires that 'LoginResponse' conform to 'Decodable'


可行解决方案

方案1:iOS侧扩展KMM生成类手动实现Decodable

无需修改KMM公共层代码,直接在iOS项目中给导入的类加扩展实现协议,适合临时Mock、测试场景使用:

extension LoginResponse: Decodable {
    enum CodingKeys: String, CodingKey {
        case data
    }
    
    public convenience init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let body = try container.decode(LoginResponseBody.self, forKey: .data)
        self.init(data: body)
    }
}

extension LoginResponseBody: Decodable {
    enum CodingKeys: String, CodingKey {
        case accessToken, lastChannel, lastDateTime, userId, transactionalKeyStatus, accessStatus
    }
    
    public convenience init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let accessToken = try container.decode(String.self, forKey: .accessToken)
        let lastChannel = try container.decodeIfPresent(String.self, forKey: .lastChannel)
        let lastDateTime = try container.decodeIfPresent(String.self, forKey: .lastDateTime)
        let userId = try container.decodeIfPresent(String.self, forKey: .userId)
        let transactionalKeyStatus = try container.decodeIfPresent(String.self, forKey: .transactionalKeyStatus)
        let accessStatus = try container.decodeIfPresent(String.self, forKey: .accessStatus)
        self.init(accessToken: accessToken, lastChannel: lastChannel, lastDateTime: lastDateTime, userId: userId, transactionalKeyStatus: transactionalKeyStatus, accessStatus: accessStatus)
    }
}

方案2:KMM侧提供统一解析方法暴露给iOS调用

KMM侧DTO已经添加了kotlinx.serialization的@Serializable注解,直接在公共层封装解析方法,双端共用同一套解析逻辑,避免两端解析结果不一致,适合生产环境使用:

  1. KMM侧代码:
class LoginResponseParser {
    private val jsonDecoder = Json { ignoreUnknownKeys = true }
    
    fun parseLoginResponse(jsonStr: String): LoginResponse {
        return jsonDecoder.decodeFromString(LoginResponse.serializer(), jsonStr)
    }
}
  1. iOS侧调用代码:
let jsonStr = String(data: data, encoding: .utf8)!
let loginData = LoginResponseParser().parseLoginResponse(jsonStr: jsonStr)
success(loginData)

方案3:iOS侧手动转换JSON生成对象

仅适合极少量临时Mock场景,不需要写额外适配代码:

// JSON转字典
let jsonDict = try! JSONSerialization.jsonObject(with: data) as! [String: Any]
let bodyDict = jsonDict["data"] as! [String: Any]
// 手动初始化DTO
let body = LoginResponseBody(
    accessToken: bodyDict["accessToken"] as! String,
    lastChannel: bodyDict["lastChannel"] as? String,
    lastDateTime: bodyDict["lastDateTime"] as? String,
    userId: bodyDict["userId"] as? String,
    transactionalKeyStatus: bodyDict["transactionalKeyStatus"] as? String,
    accessStatus: bodyDict["accessStatus"] as? String
)
let loginData = LoginResponse(data: body)
success(loginData)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 11:57:03