Let's suppose this json which represents multilingual words:
[{
"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"
}
}]
This json has items related to each others in the "trans" (translation) nested container.
My class is straight forward
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...
}
}
What is the fastest (CPU) way to JSON Decode it?
My way of handling it "feels" wrong:
let jsonDecoder = JSONDecoder()
let words = jsonDecoder.decode([Word].self, from: data)
But the words don't have any translation linked because they are not "known" during the real-time parsing.
In my example, when we parse the first word "cat", we still don't know the French nor Russian words yet.
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. */
This double decoding feels like an overkill. Isn't there anyway to search into the json data directly?
Decode first, generate your structure later. You are trying to combine the two which does not make sense.
Your first decoding does the actual decoding, your second decoding does only the linking.
Instead of that, decode to temporary structures, build a dictionary of identifiers and use that to link them to final objects.
To be honest, there is no need to do the actual linking. It could still be completely dynamic, using a dictionary.
One possible approach:
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)