Есть ли способ распарсить JSON с большой вложенностью через JSONDecoder в плоскую структуру?


#1

Есть вот такой json.

{
  "data": [
    {
      "id": "4",
      "type": "set",
      "attributes": {
        "name": "Foo",
        "description": "bar"
      }
    },
    {
      "id": "5",
      "type": "set",
      "attributes": {
        "name": "Foo 1",
        "description": "Bar 1"
      }
    }
  ]
}

Как видно в нем есть атрибуты, “data”, “attributes” которые мне в структуре не нужны. Можно ли его как-то распарсить через JSONDEcoder Т.е. вручную словарь не перебирать, а собрать свою структуру, вот по типу такого json-а.

{
[
    {
      "id": "4",
      "type": "set",
        "name": "Foo",
        "description": "bar"
    },
    {
      "id": "5",
      "type": "set",
        "name": "Foo 1",
        "description": "Bar 1"
    }
  ]
}

Т.е. после парсинга должен вернуться массив таких структур:

struct Foo: Codable {
  let id: String
  let type: String
  let name: String
  let description: String
}

#2
let json = """
{
    "data": [
        {
            "id": "4",
            "type": "set",
            "attributes": {
            "name": "Foo",
            "description": "bar"
        }
    },
        {
            "id": "5",
            "type": "set",
            "attributes": {
            "name": "Foo 1",
            "description": "Bar 1"
        }
    }
    ]
}
"""

struct Foo {
    let id: String
    let type: String
    let name: String
    let description: String
}

extension Foo: Decodable {
    private struct Key: CodingKey {
        var stringValue: String
        init?(stringValue: String) { self.stringValue = stringValue }
        var intValue: Int?
        init?(intValue: Int) { return nil }
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Key.self)
        id = try container.decode(String.self, forKey: Key(stringValue: "id")!)
        type = try container.decode(String.self, forKey: Key(stringValue: "type")!)
        let attributes = try container.decode([String: String].self, forKey: Key(stringValue: "attributes")!)
        name = attributes["name", default: ""]
        description = attributes["description", default: ""]
    }
}

struct FooContainer: Decodable {
    let data: [Foo]
}

let items = try! JSONDecoder().decode(FooContainer.self, from: json.data(using: .utf8)!).data
print(items)

struct Foo2: Decodable {
    let id: String
    let type: String
    private let attributes: [String: String]
    var name: String {
        return attributes["name", default: ""]
    }
    var description: String {
        return attributes["description", default: ""]
    }
}

struct FooContainer2: Decodable {
    let data: [Foo2]
}

let items2 = try! JSONDecoder().decode(FooContainer2.self, from: json.data(using: .utf8)!).data
items2.forEach {
    print("id: \($0.id) type: \($0.type) name: \($0.name) description: \($0.description)")
}


#3

Если можно использовать библиотеку, убил бы за такую пелену кода.

Через ObjectMapper это делается легко. Нужно всего 2 структуры

struct Data: Mppable {
    var data: [Foo] = []

    required init?(map: Map) {}

    // Mappable
    func mapping(map: Map) {
        data <- map["data"]
    }
}

struct Foo: Mappable {
    let id: String
    let type: String
    let name: String
    let description: String

    required init?(map: Map) {}

    // Mappable
    func mapping(map: Map) {
        id <- map["id"]
        type <- map["type"]
        name <- map["attributes.name"]
        description <- map["attributes.description"]
    }
}

let data = Data(JSONString: jsonstring)

#4

Там два варианта)



#5

Пардон :slight_smile:
Нужно было бы разделить что ли.


#6

Лучше так зарефакторить:

extension Foo: Decodable {
    init(from decoder: Decoder) throws {
        enum Key: String, CodingKey { case id, type, attributes }
        let container = try decoder.container(keyedBy: Key.self)
        id = try container.decode(String.self, forKey: .id)
        type = try container.decode(String.self, forKey: .type)
        struct Attributes: Decodable { let name: String, description: String }
        let attributes = try container.decode(Attributes.self, forKey: .attributes)
        name = attributes.name
        description = attributes.description
    }
}