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注解,直接在公共层封装解析方法,双端共用同一套解析逻辑,避免两端解析结果不一致,适合生产环境使用:
- KMM侧代码:
class LoginResponseParser { private val jsonDecoder = Json { ignoreUnknownKeys = true } fun parseLoginResponse(jsonStr: String): LoginResponse { return jsonDecoder.decodeFromString(LoginResponse.serializer(), jsonStr) } }
- 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
相关产品推荐
相关产品推荐

