如何用Realm解码对应模型,且让模型同时兼容Codable协议解析JSON数据
实现方案
核心修改逻辑
Realm本身有以下两个限制,需要先适配:
- 存储
Object子类的有序集合不能使用普通SwiftArray,必须使用Realm提供的List<T>容器类型 - 原生不支持
Dictionary类型持久化,需要将字典转换为自定义Object子类的List集合存储
同时手动实现Codable的编解码逻辑,完成JSON结构和Realm支持的存储结构的双向转换即可。
完整修改后代码
首先新增用来存储键值对的SectionItem模型,用来替代原来的字典结构:
import RealmSwift enum QuestionSections: String, Codable { case A = "A", B = "B", C = "C", D = "D" } class SectionItem: Object, Codable { @objc dynamic var key: String = "" @objc dynamic var value: String = "" convenience init(key: String, value: String) { self.init() self.key = key self.value = value } }
修改Question模型,替换普通数组为RealmList,并实现自定义编解码:
class Question: Object, Codable, Identifiable { @objc dynamic var title: String = "" @objc dynamic var id: String = "" let questions = List<QuestionList>() enum CodingKeys: String, CodingKey { case title, id, questions } required convenience init(from decoder: Decoder) throws { self.init() let container = try decoder.container(keyedBy: CodingKeys.self) title = try container.decode(String.self, forKey: .title) id = try container.decode(String.self, forKey: .id) let questionList = try container.decode([QuestionList].self, forKey: .questions) questions.append(objectsIn: questionList) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(title, forKey: .title) try container.encode(id, forKey: .id) try container.encode(Array(questions), forKey: .questions) } }
修改QuestionList模型,替换字典为List<SectionItem>,并实现自定义编解码:
class QuestionList: Object, Codable, Identifiable { @objc dynamic var id: String = "" @objc dynamic var question: String = "" @objc dynamic var isQuestionImage: Bool = false @objc dynamic var isSectionImage: Bool = false @objc dynamic var imageURL: String = "" @objc dynamic var imageData: Data? let sections = List<SectionItem>() @objc dynamic var selected: String = "" @objc dynamic var correct: String = "" // 便捷读取字典结构的计算属性 var sectionsDict: [QuestionSections.RawValue: String] { var dict = [String: String]() sections.forEach { dict[$0.key] = $0.value } return dict } enum CodingKeys: String, CodingKey { case id, question, isQuestionImage, isSectionImage, imageURL, imageData, sections, selected, correct } required convenience init(from decoder: Decoder) throws { self.init() let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) question = try container.decode(String.self, forKey: .question) isQuestionImage = try container.decode(Bool.self, forKey: .isQuestionImage) isSectionImage = try container.decode(Bool.self, forKey: .isSectionImage) imageURL = try container.decode(String.self, forKey: .imageURL) imageData = try container.decodeIfPresent(Data.self, forKey: .imageData) selected = try container.decode(String.self, forKey: .selected) correct = try container.decode(String.self, forKey: .correct) // 将JSON解码得到的字典转换为SectionItem集合 let sectionDict = try container.decode([String: String].self, forKey: .sections) let items = sectionDict.map { SectionItem(key: $0.key, value: $0.value) } sections.append(objectsIn: items) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(question, forKey: .question) try container.encode(isQuestionImage, forKey: .isQuestionImage) try container.encode(isSectionImage, forKey: .isSectionImage) try container.encode(imageURL, forKey: .imageURL) try container.encodeIfPresent(imageData, forKey: .imageData) try container.encode(selected, forKey: .selected) try container.encode(correct, forKey: .correct) // 将存储的SectionItem集合转换为字典再编码为JSON var sectionDict = [String: String]() sections.forEach { sectionDict[$0.key] = $0.value } try container.encode(sectionDict, forKey: .sections) } }
顶层QuestionContainer不需要修改,直接保留原实现即可,如果你需要将QuestionContainer也持久化到Realm,只需要参照上述Question的修改逻辑,将其继承自Object,把questionCategories替换为List<Question>再添加对应的编解码逻辑即可。
注意事项
- 所有Realm
Object的存储属性必须提供默认初始值,否则会触发运行时崩溃 - 如果使用Realm 10.0+版本,可以用
@Persisted属性包装器替代@objc dynamic和let声明List,代码写法更简洁,上述逻辑仍然适用 - 新增的
sectionsDict计算属性可以直接拿到原来的字典结构,不需要每次手动转换
内容的提问来源于stack exchange,提问作者Ufuk Köşker
相关产品推荐
相关产品推荐

