Как мне декодировать объект json с помощью JSONDecoder, если я не уверен в ключах

У меня есть ответ api в следующей форме -

  {
   "textEntries":{
      "summary":{
         "id":"101e9136-efd9-469e-9848-132023d51fb1",
         "text":"some text",
         "locale":"en_GB"
      },
      "body":{
         "id":"3692b0ec-5b92-4ab1-bc25-7711499901c5",
         "text":"some other text",
         "locale":"en_GB"
      },
      "title":{
         "id":"45595d27-7e06-491e-890b-f50a5af1cdfe",
         "text":"some more text again",
         "locale":"en_GB"
      }
   }
}

Я хотел бы декодировать это через JSONDecoder, чтобы использовать свойства. У меня есть проблема с ключами, в данном случае _3 _, _ 4_ и title генерируются где-то еще, а не всегда эти значения, они всегда уникальны, но основаны на логике, которая имеет место в другом месте продукта, так что другой вызов статьи с другим содержанием может вернуть leftBody или subTitle и т. д.

Модель для тела этих реквизитов всегда одна и та же, однако я могу ожидать, что одни и те же поля будут существовать в любой комбинации ответов.

Мне нужно будет иметь доступ к телу каждого ключа в коде где-нибудь еще. Другой ответ API скажет мне ключ, который мне нужен.

Я не уверен, как я могу справиться с этим с помощью Decodable, поскольку я не могу ввести значения заранее.

Я думал о моделировании тела -

struct ContentArticleTextEntries: Decodable {
    var id: String
    var text: String
    var locale: Locale
}

и сохранение значений в структуре вроде -

struct ContentArticle: Decodable {
    var textEntries: [String: ContentArticleTextEntries]


    private enum CodingKeys: String, CodingKey {
        case textEntries
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        self.textEntries = try values.decode(ContentArticleTextEntries.self, forKey: .textEntries)

    }

}

Я мог бы использовать индекс в другом месте для доступа к свойству, однако я не знаю, как декодировать в эту форму, поскольку приведенное выше не сработает.

Так что позже я, например, получу доступ к textEntries["body"].

Я также не знаю, есть ли лучший способ справиться с этим.

Я думал о преобразовании ключей в «тип» с помощью перечисления, но опять же, незнание случаев перечисления заранее делает это невозможным.

Я знаю, что textEntries не меняется, и я знаю, что идентификатор, текст и языковой стандарт не меняются. Я не знаю ключей между этим слоем. Я пробовал полезное решение, опубликованное @vadian, но, похоже, не могу выполнить эту работу в контексте того, что нужно декодировать только 1 набор ключей.


person Teddy K    schedule 20.11.2019    source источник
comment
Можете ли вы вставить свои полные данные json   -  person Radhe Yadav    schedule 20.11.2019
comment
Извините, я пропустил некоторые фигурные скобки, я обновил действительный ответ json   -  person Teddy K    schedule 20.11.2019
comment
Что вы имеете в виду, говоря «сопоставить значения в этом ответе со значениями из другого ответа»?   -  person PGDev    schedule 20.11.2019
comment
Не обращайте на это внимания, возможно, я запуталась в своей проблеме. Я просто имел в виду, что мне нужно получить доступ к свойству textEntries с помощью ключа, который я могу получить откуда-нибудь. например, как только я получу эти значения, другой ответ может потребовать от меня поиска body и т. д.   -  person Teddy K    schedule 20.11.2019
comment
По теме: stackoverflow.com/questions/54129682/, довольно умное решение.   -  person vadian    schedule 20.11.2019
comment
рассмотрите возможность использования nestedContainer: let nestedValues = try decoder.nestedContainer(keyedBy: NestedCodingKeys.self, forKey: .WhatEverYourKeyIs)   -  person Jafar Khoshtabiat    schedule 20.11.2019
comment
Вы знаете ключ верхнего уровня? textEntries? Это всегда одно и то же? И дочерние элементы тоже имеют такую ​​же форму? Значит, в этом случае вы не знаете, какими будут ключи, представленные body, summary и т. Д.?   -  person nodediggity    schedule 21.11.2019
comment
Да, я знаю, textEntries это не изменится, и я знаю id, text и locale, это не изменится. Я не знаю ключей между этим слоем. Я пробовал полезное решение, опубликованное @vadian, но, похоже, не могу выполнить эту работу в контексте того, что нужно декодировать только 1 набор ключей.   -  person Teddy K    schedule 21.11.2019


Ответы (3)


Для предлагаемого решения в этот ответ структуры

struct ContentArticleTextEntries: Decodable {
    let title : String
    let id: String
    let text: String
    let locale: Locale

    enum CodingKeys: String, CodingKey {
        case id, text, locale
    }

    init(from decoder: Decoder) throws {
        self.title = try decoder.currentTitle()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(String.self, forKey: .id)
        self.text = try container.decode(String.self, forKey: .text)
        let localeIdentifier = try container.decode(String.self, forKey: .locale)
        self.locale = Locale(identifier: localeIdentifier)
    }
}     

struct ContentArticle: TitleDecodable {
    let title : String
    var elements: [ContentArticleTextEntries]
}

struct Container: Decodable {
    let containers: [ContentArticle]
    init(from decoder: Decoder) throws {
        self.containers = try decoder.decodeTitledElements(ContentArticle.self)
    }
}

Затем декодируйте Container.self

person vadian    schedule 21.11.2019
comment
Спасибо, это огромная помощь, это создает массив из [textEntries] - возможно ли, чтобы textEntries был одной опорой с массивом ContentArticleTextEntries в качестве его значения? Я очень ценю вашу помощь в этом. Любые указания о том, как это исправить, мне очень помогут. - person Teddy K; 21.11.2019
comment
Вы должны изменить Container на let containers: [ContentArticleTextEntries] вместо этого, я считаю - person nodediggity; 21.11.2019
comment
Вы получаете массив ContentArticleTextEntries с containers.first!.elements - person vadian; 21.11.2019
comment
Огромное спасибо. Нет слов, спасибо. Я многому научился из этого. - person Teddy K; 21.11.2019
comment
Все еще спасаю жизни! ???? - person Codetard; 22.09.2020

Если ваши модели нравятся,

struct ContentArticle: Decodable {
    let textEntries: [String: ContentArticleTextEntries]
}

struct ContentArticleTextEntries: Decodable {
    var id: String
    var text: String
    var locale: String
}

Затем вы можете просто получить доступ к данным на основе key, например,

let response = try JSONDecoder().decode(ContentArticle.self, from: data)
let key = "summary"
print(response.textEntries[key])

Примечание. Нет необходимости писать enum CodingKeys и init(from:), если при синтаксическом анализе JSON не требуется специальной обработки.

person PGDev    schedule 20.11.2019
comment
Как мне взять ответ JSON и создать [String: ContentArticleTextEntries]? ContentArticleTextEntries - это тело каждого поля, мне понадобится ключ каждого поля, чтобы добавить мой dict? - person Teddy K; 20.11.2019
comment
Вы, должно быть, получаете ответ data, верно? Я добавил, как вы можете анализировать эти данные. В приведенном выше коде response относится к типу [String: ContentArticleTextEntries]. - person PGDev; 20.11.2019
comment
Ni, боюсь, что нет, vadian опубликовал комментарий, который указывал на решение, но я не смог заставить это работать в этом случае. Проблема, с которой я столкнулся, заключается в том, что я не знаю ключей, которые пытаюсь декодировать, например, в моем примере summary и body могут быть любыми, однако их реквизиты ContentArticleTextEntries всегда будут такой формы. Любая помощь была бы потрясающей, пожалуйста. - person Teddy K; 21.11.2019

Используйте вариантный метод «decodeIfPresent» вместо метода декодирования, также вам необходимо разбить словарь ContentArticleTextEntries на отдельные ключи:

struct ContentArticle: Decodable {
    var id: String
    var text: String?
    var locale: String?

    private enum CodingKeys: String, CodingKey {
        case id, text, locale
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decodeIfPresent(String.self, forKey: .id) ?? ""
        self.text = try container.decodeIfPresent(String.self, forKey: .text)
        self.locale = try container.decodeIfPresent(String.self, forKey: .locale) 
    }

}
person Shubham Ojha    schedule 20.11.2019