Как правильно разобрать JSON с элементом root как массив в Swift? - PullRequest
2 голосов
/ 09 июля 2020

У меня следующая проблема, из-за которой у меня нет правильной структуры для декодирования ответа от node-red для состояния моих плееров sonos: decode выглядит следующим образом:

typealias Response = [sonosData]

struct sonosData: Codable {
    let  sonos: [sonosStatusEntry]?
}

struct sonosStatusEntry: Codable {
    let status: [sonosStatus]?
}

struct sonosStatus: Codable {
    let urlHostname: String
    let urlPort: Int
    let baseUrl: String
    let sonosName: String
    let uuid: String
    let invisible: Bool
}

let response = try JSONDecoder().decode(Response.self, from: data)

Я получаю следующую ошибку в Swift:

Failed to load: The data couldn’t be read because it isn’t in the correct format.

Есть предложения?

Ответы [ 3 ]

0 голосов
/ 09 июля 2020

У вас 2 ошибки в вашем коде:

  1. Данные, которые вы пытаетесь декодировать, - [[sonosStatus]]. поэтому вам нужно декодировать как:
let response = try JSONDecoder().decode([[sonosStatus]].self, from: data)
urlPort представляет как String, так и Int. Поэтому вам нужно либо исправить свой формат JSON, либо поддержать оба формата с помощью специального декодера.

Вы можете добиться этого с помощью этой простой оболочки свойств:

@propertyWrapper
struct SomeKindOfInt: Codable {
    var wrappedValue: Int?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let stringifiedValue = try? container.decode(String.self) {
            wrappedValue = Int(stringifiedValue)
        } else {
            wrappedValue = try container.decode(Int.self)
        }
    }
}

А затем используя это как:

struct sonosStatus: Codable {
    let urlHostname: String
    @SomeKindOfInt var urlPort: Int?
    let baseUrl: String
    let sonosName: String
    let uuid: String
    let invisible: Bool
}
0 голосов
/ 09 июля 2020

Вы можете использовать JSONSerialization с параметром чтения .allowFragments.

let objects = (попробуйте? JSONSerialization.jsonObject (с: данными, параметрами: .allowFragments)) как? [[NSDictionary]]

И это было исправлено в последней версии Swift: https://bugs.swift.org/browse/SR-6163

0 голосов
/ 09 июля 2020

Одна из проблем связана с urlPort. Ваш json имеет значения Int и String для urlPort. Вы можете определить собственный init для обработки этого.

struct sonosStatus: Codable {
    let urlHostname: String
    let urlPort: Int
    let baseUrl: String
    let sonosName: String
    let uuid: String
    let invisible: Bool    
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        urlHostname = try values.decode(String.self, forKey: .urlHostname)
        baseUrl = try values.decode(String.self, forKey: .baseUrl)
        sonosName = try values.decode(String.self, forKey: .sonosName)
        uuid = try values.decode(String.self, forKey: .uuid)
        invisible = try values.decode(Bool.self, forKey: .invisible)
        
        // you can define urlPort as optional or unwrap it.
        if let intVal = try? values.decode(Int.self, forKey: .urlPort) {
            urlPort = intVal
        } else if let stringVal = try? values.decode(String.self, forKey: .urlPort) {
            urlPort = Int(stringVal) ?? 0
        } else {
            urlPort = ""
        }
    }
}

А также ваш json является массивом sonosStatus array.

Итак, вам нужно изменить Response typealias, как показано ниже :

typealias Response = [[sonosStatus]]
...