Я создал следующий образец блока JSON. Любое значение, оканчивающееся буквой, является динамическим.
{
"groupName": {
"groupA": {
"fields": {
"fieldA": "valueA",
"fieldB": "valueB"
},
"letters": {
"letterA: "A"
}
},
"groupB": {
"fields": {
"fieldC": "valueC",
"fieldD": "valueD"
},
"letters": {
"letterB: "B"
}
}
}
}
Моя цель — использовать Decodable, чтобы я мог считывать эти данные в struct, которые я определил.
Ниже моя текущая работа содержится в файле игровой площадки, который я использую, чтобы попытаться решить эту проблему:
import Foundation
let jsonString = "{\"groupName\":{\"groupA\":{\"fields\":{\"fieldA\":\"valueA\",\"fieldB\":\"valueB\"},\"letters\":{\"letterA:\"A\"}},\"groupB\":{\"fields\":{\"fieldC\":\"valueC\",\"fieldD\":\"valueD\"},\"letters\":{\"letterB:\"B\"}}}}"
struct CustomCodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }
static let field = CustomCodingKeys.make(key: "field")
static func make(key: String) -> CustomCodingKeys {
return CustomCodingKeys(stringValue: key)!
}
}
// Values
struct Field {
let field: String
let value: String
}
struct Letter: Decodable {
let title: String
let letter: String
}
// Value holders
struct FieldData: Decodable {
var fields: [Field]
init(from decoder: Decoder) throws {
self.fields = [Field]()
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
for key in container.allKeys {
print("processing field: \(key.stringValue)")
let dynamicKey = CustomCodingKeys.make(key: key.stringValue)
let value = try container.decode(String.self, forKey: dynamicKey)
let field = Field(field: key.stringValue,
value: value)
fields.append(field)
}
}
}
struct LetterData: Decodable {
var letters: [Letter]
init(from decoder: Decoder) throws {
self.letters = [Letter]()
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
for key in container.allKeys {
print("processing letter: \(key.stringValue)")
let dynamicKey = CustomCodingKeys.make(key: key.stringValue)
let value = try container.decode(String.self, forKey: dynamicKey)
let letter = Letter(title: key.stringValue,
letter: value)
letters.append(letter)
}
}
}
// Containers
struct Group: Decodable {
var name: String!
var groups: [GroupData]
init(from decoder: Decoder) throws {
self.groups = [GroupData]()
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
for key in container.allKeys {
print("processing section: \(key.stringValue)")
let group = try container.decode(GroupData.self,
forKey: key)
groups.append(group)
}
}
}
struct GroupData: Decodable {
var fieldData: FieldData
var letterData: LetterData
enum CodingKeys: String, CodingKey {
case fieldData = "fields"
case letterData = "letters"
}
}
struct GroupList: Decodable {
struct GroupName: Decodable {
var name: String!
var groups: [Group]
init(from decoder: Decoder) throws {
self.groups = [Group]()
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
for key in container.allKeys {
let name = key.stringValue
self.name = name
print("processing group: \(String(describing: self.name))")
var group = try container.decode(Group.self,
forKey: key)
group.name = name
groups.append(group)
}
}
}
let groupName: GroupName
}
let decoder = JSONDecoder()
if let data = jsonString.data(using: .utf8),
let groupList = try? decoder.decode(GroupList.self,
from: data) {
print("group list created")
}
В моей структуре GroupData я могу удалить переменные, а затем реализовать init(from decoder: Decoder) throws, который при настройке с правильным поиском (инициализация FieldData и LetterData) может идентифицировать правильные пары. Однако он не заполняет правильные структуры значений.





Я использую этот https://app.quicktype.io/, чтобы помочь мне конвертировать их. Я проверил ваш вывод, и он неверен в строке:
"letters": {
"letterA: "A"
}
Правильный :
"letterA": "A"
В букве Б встречаются такие же.
Проверьте свой вывод правильно:
{
"groupName": {
"groupA": {
"fields": {
"fieldA": "valueA",
"fieldB": "valueB"
},
"letters": {
"letterA": "A"
}
},
"groupB": {
"fields": {
"fieldC": "valueC",
"fieldD": "valueD"
},
"letters": {
"letterB": "B"
}
}
}
}
Спасибо @marcos. Это была ошибка из-за того, что я неправильно воссоздал производственный JSON.
У вас небольшая ошибка в расшифровке Group. Вы стремитесь декодировать все ключи для группы внутри Group, а также переходите дальше к декодированию GroupData, которое само имеет «поля» и «буквы». Используйте контейнер с одним значением внутри Group, и все должно быть в порядке.
Вот как должен выглядеть ваш Group,
struct Group: Decodable {
var name: String!
var groups: GroupData
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
groups = try container.decode(GroupData.self)
}
}
Обратите внимание, сам ваш json неверен, я его отформатировал, и он должен быть таким,
let jsonString = "{\"groupName\":{\"groupA\":{\"fields\":{\"fieldA\":\"valueA\",\"fieldB\":\"valueB\"},\"letters\":{\"letterA\":\"A\"}},\"groupB\":{\"fields\":{\"fieldC\":\"valueC\",\"fieldD\":\"valueD\"},\"letters\":{\"letterB\":\"B\"}}}}"
Спасибо @sandeep. JSON был просто моей ошибкой при создании примера кода, но настоящий JSON действителен, и я должен был это понять!
Честно говоря, я бы посоветовал вам перенести «динамические» ключи в данные и использовать списки вместо объектов. Это сделает ваш синтаксический анализ намного проще, и вы получите гораздо больше структуры, чем простой
[String:Any], который является кошмаром кастинга. Вы пытаетесь делать что-то отличное от «мейнстрима», что обычно является Хорошей Вещью (ТМ), но в этом случае вы создаете себе больше проблем, чем того стоит.