使用Codable解析嵌套的JSON,如ObjectMapper Swift 4

时间:2018-05-24 06:41:06

标签: json swift codable decodable

我有一个JSON字符串,我想解析它像ObjectMapper使用Codable协议。

    struct Health: Mappable {
    var size: [String : Any] = [:]?
    var name: Double?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {
        size    <- map["health.size"]
        name    <- map["health.name"]
    }
}

我希望消除直接访问的健康结构模型,因为为不同的属性创建每个模型结构。

let jsonString = """
 {
    "health": {
        "size":{
            "width":150,
            "height":150
            },
        "name":"Apple"
        }
 }
 """ 

我想使用(。)Dot运算符访问属性,例如health.size,而不创建健康的结构模型。

struct HealthType: Codable {
    var health: Health
 }

struct Health: Codable {
        var title: String
        var size: Size

        enum CodingKeys: String, CodingKey
        {
            case title = "name"
        }
     }

     struct Size: Codable {
        var width: Double
        var height: Double
     }

4 个答案:

答案 0 :(得分:1)

为此,您需要自己实施Codable协议。这并不太难:

在Playground上尝试以下操作。

import Foundation

struct HealthType: Codable {
    let title: String
    let width: Double
    let height: Double

    enum CodingKeys: String, CodingKey
    {
        case health = "health"
        case title = "name"
        case width = "width"
        case height = "height"
        case size = "size"
    }
}

extension HealthType {

    init(from decoder: Decoder) throws {
        let healthTypeContainer = try decoder.container(keyedBy: CodingKeys.self)
        let health = try healthTypeContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .health)
        let size = try health.nestedContainer(keyedBy: CodingKeys.self, forKey: .size)
        let title = try health.decode(String.self, forKey: .title)
        let width = try size.decode(Double.self, forKey: .width)
        let height = try size.decode(Double.self, forKey: .height)
        self.init(title: title, width: width, height: height)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        var health = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .health)
        var size = health.nestedContainer(keyedBy: CodingKeys.self, forKey: .size)
        try health.encode(title, forKey: .title)
        try size.encode(width, forKey: .width)
        try size.encode(height, forKey: .height)
    }
}

let jsonData = """
{
"health": {
    "size":{
        "width":150,
        "height":150
    },
    "name":"Apple"
}
}
""".data(using: .utf8)!
do {
    print(jsonData)
    let healthType = try JSONDecoder().decode(HealthType.self, from: jsonData)
    print(healthType.title)  // Apple
    print(healthType.width)  // 150.0
    print(healthType.width)  // 150.0
} catch {
    print(error)
}

答案 1 :(得分:0)

你几乎可以完成这项工作。但是,JSONDecoderData而不是String上运行,默认情况下使用UTF-8对Playground进行编码,以便运行以下Playground:

import Cocoa

let jsonData = """
    {
        "health": {
            "size":{
                "width":150,
                "height":150
           },
           "name":"Apple"
        }
    }
    """.data(using: .utf8)!

struct HealthType: Codable {
    var health: Health
}

struct Health: Codable {
    var title: String
    var size: Size

    enum CodingKeys: String, CodingKey
    {
        case title = "name"
        case size
    }
}

struct Size: Codable {
    var width: Double
    var height: Double
}

do {
    let health = try JSONDecoder().decode(HealthType.self, from:jsonData)
    print(health)
    let h = health.health
    print(h.title)

} catch {
    print(error)
}

虽然这解析并运行良好但我无法理解你的陈述并且没有创建健康的结构模型&#34;。使用Codable的部分权衡是您必须提供JSON相关部分的结构定义。您也可以将输入解析为[String:Any].self,但使用它是一种阻力。你将不得不经常评估演员表和期权。使用Codable协议解析您的错误时,所有内容都会集中在decode可能throw的内容中。您获得的相关信息非常适合描述JSON(或struct中的错误,具体取决于您的观点)。

简而言之:只要你的JSON包含你的"health"密钥,你就必须告诉它如何处理它。

答案 2 :(得分:0)

如果您的意图是 不公开我不需要的属性 ,那么一种方法是私下解码整个结构,然后只使用那些属性将在外部世界曝光。

struct Health {
    let size: Size
    let title: String

    struct Size: Decodable {
        let width: Int
        let height: Int
    }
    private struct RawResponse: Decodable {
        let health: PrivateHealth
        struct PrivateHealth: Decodable {
            let size: Size
            let name: String
        }
    }
}

// Decodable requirement is moved to extension so that default initializer is accessible
extension Health: Decodable {
    init(from decoder: Decoder) throws {
        let response = try RawResponse(from: decoder)
        size = response.health.size
        title = response.health.name
    }
}

<强>用法:

let jsonData = """
{
    "health": {
        "size":{
            "width":150,
            "height":150
        },
        "name":"Apple"
    }
}
""".data(using: .utf8)!

do {
    let health = try JSONDecoder().decode(Health.self, from: jsonData)
    print(health.size)  // Size(width: 150, height: 150)
    print(health.title)  // Apple
} catch {
    print(error.localizedDescription)
}

编辑:如果您还需要编码功能

如果您还需要实施Encodable协议,以便可以像实际响应那样对数据进行编码,那么您可以使用以下方法进行编码:

extension Health: Encodable {
    func encode(to encoder: Encoder) throws {
        let health = Health.RawResponse.PrivateHealth(size: size, name: title)
        let response = RawResponse(health: health)
        var container = encoder.singleValueContainer()
        try container.encode(response)
    }
}
  

为了实现这一目标,您需要使RawResponsePrivateHealthSize结构也可以编码

  • 更改为 private struct RawResponse: Encodable, Decodable {
  • 更改为 struct PrivateHealth: Encodable, Decodable {
  • 更改为 struct Size: Encodable, Decodable {

编码示例:

let health = Health(size: Health.Size(width: 150, height: 150), title: "Apple")
do {
    let encodedHealth = try JSONEncoder().encode(health)  // Encoded data
    // For checking purpose, you convert the data to string and print
    let jsonString = String(data: encodedHealth, encoding: .utf8)!
    print(jsonString)  // This will ensure data is encoded as your desired format
} catch {
    print(error.localizedDescription)
}

答案 3 :(得分:0)

嘿,我创建了KeyedCodable,我认为这正是您所寻找的。您的实现将如下所示。

struct Health: Codable, Keyedable {
    var size: [String: Int]!
    var name: String?

    mutating func map(map: KeyMap) throws {
        try size <-> map["health.size"]
        try name <-> map["health.name"]
    }

    init(from decoder: Decoder) throws {
        try KeyedDecoder(with: decoder).decode(to: &self)
    }
}