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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 23:54:02