如何 json 解码链接的相关项目?

How to json decode linked, related items?

让我们假设这个 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很直接

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)的方法是什么?

我的处理方式"feels"错误:

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

但是这些词没有任何翻译链接,因为它们在实时解析期间不是"known"。

在我的示例中,当我们解析第一个单词 "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数据不行吗?

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

您的第一次解码执行实际解码,您的第二次解码仅执行 linking。

取而代之的是,解码为临时结构,构建标识符字典并将其用于link最终对象。

老实说,没有必要做实际的 linking。它仍然可以是完全动态的,使用字典。

一种可能的方法:

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[[=10=]] }
    }
}

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: [=10=].id, value: [=10=]) })

        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)