Разобрать Json с неизвестными переменными - PullRequest
0 голосов
/ 15 октября 2019

Я пытаюсь разобрать Json с неизвестными ключами. Json выглядит следующим образом:

{[
  "stats": {
    "total": {
      "dagegen gestimmt": 196,
      "nicht beteiligt": 41,
      "dafür gestimmt": 435,
      "enthalten": 37
    },
    "party_A": {
      "dagegen gestimmt": 90,
      "nicht beteiligt": 2
    },
    "party_B": {
      "dafür gestimmt": 230,
      "nicht beteiligt": 16
    },
    "party_C": {
      "dagegen gestimmt": 1,
      "nicht beteiligt": 1
    },
    "party_D": {
      "dagegen gestimmt": 31,
      "enthalten": 35,
      "nicht beteiligt": 1
    },
    "party_E": {
      "dagegen gestimmt": 64,
      "nicht beteiligt": 5
    },
  }
]}

Проблема в том, что A: не у каждого элемента «stats» одинаковые стороны, а B: из 4 возможных ключей (видно из «total») не вседолжен быть в партийном объекте.

Ответы [ 2 ]

0 голосов
/ 15 октября 2019

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

Во-первых, вот JSON, который, я полагаю, вы имеете в виду(Я удалил лишние [], которые синтаксически некорректны):

let json = Data("""
{
  "stats": {
    "total": {
      "dagegen gestimmt": 196,
      "nicht beteiligt": 41,
      "dafür gestimmt": 435,
      "enthalten": 37
    },
    "party_A": {
      "dagegen gestimmt": 90,
      "nicht beteiligt": 2
    },
    "party_B": {
      "dafür gestimmt": 230,
      "nicht beteiligt": 16
    },
    "party_C": {
      "dagegen gestimmt": 1,
      "nicht beteiligt": 1
    },
    "party_D": {
      "dagegen gestimmt": 31,
      "enthalten": 35,
      "nicht beteiligt": 1
    },
    "party_E": {
      "dagegen gestimmt": 64,
      "nicht beteiligt": 5
    }
  }
}
""".utf8)

Нам нужен способ для декодирования одного PartyResult. Мы не можем использовать Decodable непосредственно здесь, потому что нам нужно передать имя, но это нормально. Мы можем сделать наш собственный init, который принимает контейнер и имя. Я предполагаю, что вы хотите использовать значение по умолчанию 0 для пропущенных значений (вместо того, чтобы делать их необязательными).

struct PartyResult {
    enum CodingKeys: String, CodingKey {
        case dagegenGestimmt = "dagegen gestimmt"
        case nichtBeteiligt = "nicht beteiligt"
        case dafürGestimmt = "dafür gestimmt"
        case enthalten
    }

    let name: String
    let dagegenGestimmt: Int
    let nichtBeteiligt: Int
    let dafürGestimmt: Int
    let enthalten: Int

    init(name: String, container: KeyedDecodingContainer<CodingKeys>) throws {
        func decode(_ key: CodingKeys) throws -> Int {
           return try container.decodeIfPresent(Int.self, forKey: key) ?? 0 
        }

        self.name = name
        self.dagegenGestimmt = try decode(.dagegenGestimmt)
        self.nichtBeteiligt = try decode(.nichtBeteiligt)
        self.dafürGestimmt = try decode(.dafürGestimmt)
        self.enthalten = try decode(.enthalten)
    }
}

Как и во многих проблемах с ключом словаря - свойство>, нам понадобится какой-то видAnyKey, который может обрабатывать произвольные строки. Это простой, который обрабатывает только строки, а не целые числа.

struct AnyStringKey: CodingKey, Equatable {
    var stringValue: String
    init(stringValue: String) { self.stringValue = stringValue }
    init(_ stringValue: String) { self.init(stringValue: stringValue) }
    var intValue: Int?
    init?(intValue: Int) { return nil }
}

С этим мы можем декодировать внешний stats объект:

struct Stats: Decodable {

    // This could be replaced with AnyStringKey, but just to be explicit.
    enum CodingKeys: CodingKey { case stats }

    let partyResults: [PartyResult]

    init(from decoder: Decoder) throws {
        // Get outer container
        let outerContainer = try decoder.container(keyedBy: CodingKeys.self)

        // Get inner "stats" container
        let container = try outerContainer.nestedContainer(keyedBy: AnyStringKey.self, 
                                                           forKey: .stats)

        // Map each entry to a result
        self.partyResults = try container.allKeys
            .filter { $0 != AnyStringKey("total") } // Assuming you want to ignore the total key
            .map { name in
                try PartyResult(name: name.stringValue,
                                container: container.nestedContainer(keyedBy: PartyResult.CodingKeys.self,
                                                                     forKey: name))
        }
    }
}

let results = try JSONDecoder().decode(Stats.self, from: json).partyResults


▿ 5 elements
  ▿ __lldb_expr_20.PartyResult
    - name: "party_A"
    - dagegenGestimmt: 90
    - nichtBeteiligt: 2
    - dafürGestimmt: 0
    - enthalten: 0
  ▿ __lldb_expr_20.PartyResult
    - name: "party_D"
    - dagegenGestimmt: 31
    - nichtBeteiligt: 1
    - dafürGestimmt: 0
    - enthalten: 35
  ▿ __lldb_expr_20.PartyResult
    - name: "party_B"
    - dagegenGestimmt: 0
    - nichtBeteiligt: 16
    - dafürGestimmt: 230
    - enthalten: 0
  ▿ __lldb_expr_20.PartyResult
    - name: "party_C"
    - dagegenGestimmt: 1
    - nichtBeteiligt: 1
    - dafürGestimmt: 0
    - enthalten: 0
  ▿ __lldb_expr_20.PartyResult
    - name: "party_E"
    - dagegenGestimmt: 64
    - nichtBeteiligt: 5
    - dafürGestimmt: 0
    - enthalten: 0
0 голосов
/ 15 октября 2019

Начиная с B: Объявите элементы структуры как необязательные

struct Vote {
    let aye, no, abstained, absent : Int?
}

A: Расшифруйте stats как [String:Vote] и перечислите ключи


Если вы несете ответственность заJSON рассматривает более разумный формат, например, отправляет stats в виде массива и перемещает имя участника в Vote

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...