【问题标题】:How to json decode linked, related items?如何json解码链接的相关项目?
【发布时间】:2019-11-11 15:39:24
【问题描述】:

让我们假设这个表示多语言单词的 json:

[{
  "id": "en_cat",
  "name": "cat",
  "def": "A cat is a domestic animal of the feline family.",
  "trans": {
    "fr": "fr_chat",
    "ru": "ru_ко́шка"
  }
}, {
  "id": "fr_chat",
  "name": "chat",
  "def": "Le chat est un animal domestique de la famille des félins.",
  "trans": {
    "en": "en_cat",
    "ru": "ru_ко́шка"
  }
}, {
  "id": "ru_ко́шка",
  "name": "ко́шка",
  "def": "..."
  "trans": {
    "en": "en_cat",
    "fr": "fr_chat"
  }
}]

这个json在“trans”(翻译)嵌套容器中有相互关联的项目。

我的课很直接

class Word: Decodable {
    var id: String
    var name: String
    var definition: String
    var enTranslation: Word?
    var frTranslation: Word?
    var ruTranslation: Word?

    enum JsonCodingKey: String, CodingKey {
        case id
        case name
        case def
        case trans
    }

    enum JsonTransCodingKey: String, CodingKey {
        case en
        case fr
        case ru
    }

    convenience init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: JsonCodingKey.self)
        let id = try container.decode(String.self, forKey: .id)
        let name = try container.decode(String.self, forKey: .name)
        let definition = try container.decode(String.self, forKey: .def)
        self.init(id: id, name: name, definition: definition)

        // Tricky part here...
        let transContainer = try container.nestedContainer(keyedBy: JsonTransCodingKey.self, forKey: .trans)
        if let en = transContainer.decode(String.self, forKey: .en) {
            self.enTranslation = realm.find(wordId: en) // Singleton that looks into memory for the word
        }
        // And repeat the same if logic for the other languages...
    }
}

JSON 解码的最快 (CPU) 方式是什么?

我的处理方式“感觉”不对:

  1. 我使用解码字词
let jsonDecoder = JSONDecoder()
let words = jsonDecoder.decode([Word].self, from: data)

但是这些词没有任何翻译链接,因为它们在实时解析过程中不是“已知的”。

在我的示例中,当我们解析第一个单词“cat”时,我们仍然不知道法语或俄语单词。

  1. 所以我必须再次解码,一旦我记住了所有单词。
let jsonDecoder = JSONDecoder()
let words = jsonDecoder.decode([Word].self, from: data) // Words don't have their translations
self.saveInMemory(words)  // In my case, it is saved to Realm.
let words = jsonDecoder.decode([Word].self, from: data) 
/* Words are now linked to each others
Because during decoding, the func Word.init(from decoder) will 
look into `Realm` and find the translations. */

这种双重解码感觉有点过头了。反正不直接搜索json数据吗?

【问题讨论】:

  • 解码并不意味着生成你的最终数据结构。以对象的编码方式(“翻译”)解码对象,然后使用不同的对象将它们链接在一起。
  • JSON 表示数据树。您不能仅使用 JSON 来“链接”或引用任何内容。不过,您可以这样做 int YAML。但它仍然不能为你做所有事情。

标签: ios json swift jsondecoder


【解决方案1】:

先解码,稍后生成结构。您正在尝试将两者结合起来,这是没有意义的。

您的第一个解码执行实际解码,您的第二个解码仅执行链接。

取而代之的是,解码为临时结构,构建标识符字典并使用它将它们链接到最终对象。

说实话,没有必要进行实际的链接。它仍然可以是完全动态的,使用字典。

一种可能的方法:

let data = """
[{
  "id": "en_cat",
  "name": "cat",
  "def": "A cat is a domestic animal of the feline family.",
  "trans": {
    "fr": "fr_chat",
    "ru": "ru_ко́шка"
  }
}, {
  "id": "fr_chat",
  "name": "chat",
  "def": "Le chat est un animal domestique de la famille des félins.",
  "trans": {
    "en": "en_cat",
    "ru": "ru_ко́шка"
  }
}, {
  "id": "ru_ко́шка",
  "name": "ко́шка",
  "def": "...",
  "trans": {
    "en": "en_cat",
    "fr": "fr_chat"
  }
}]
""".data(using: .utf8)!

enum Language: String {
    case english = "en"
    case french = "fr"
    case russian = "ru"
}

class Word: Decodable {
    let id: String
    let name: String
    let definition: String
    let translationsIds: [String: String]

    weak var parentDictionary: Dictionary!

    private enum CodingKeys: String, CodingKey {
        case id
        case name
        case definition = "def"
        case translationsIds = "trans"
    }

    func translation(for language: Language) -> Word? {
        return translationsIds[language.rawValue].flatMap { parentDictionary.words[$0] }
    }
}

class Dictionary: Decodable {
    let words: [String: Word]

    required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let words = try container.decode([Word].self)
        self.words = [String: Word](uniqueKeysWithValues: words.map { (key: $0.id, value: $0) })

        for word in words {
            word.parentDictionary = self
        }
    }
}

let decoder = JSONDecoder()
let dictionary = try decoder.decode(Dictionary.self, from: data)

print(dictionary.words["fr_chat"]?.translation(for: .english)?.name)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多