解析可解码的结构属性为任何数据类型

huangapple go评论73阅读模式
英文:

Parse decodable struct property as any data type

问题

以下是要翻译的内容:

The two structs that I am using are Scoreboard and ResultSet

struct ResultSet: Codable {
    var name: String
    var headers: [String]
    //var rowSet: [String]
}

struct Scoreboard: Codable {
    var resultSets: [ResultSet]
}

The issue comes with the rowSet property of ResultSet, as this is an array of any type and length, so

[{
    "resource": "resource A",
    "rowSet": [
        ["A", 1, "test1"],
        ["B", 2, "test2"]
    ],
},
{
    "resource": "resource B",
    "rowSet": [
        ["2/28/2022", 1, 4, "loss"],
        ["3/28/2022", 2, 3, "win"]
    ],
}]

Parsing it as a string results in a parsing error. Setting the type to [AnyObject] doesn't build as it doesn't conform to Decodable

How should this be parsed?

英文:

The two structs that I am using are Scoreboard and ResultSet

struct ResultSet: Codable {
    var name: String
    var headers: [String]
    //var rowSet: [String]
}

struct Scoreboard: Codable {
    var resultSets: [ResultSet]
}

The issue comes with the rowSet property of ResultSet, as this is an array of any type and length, so

[{
    "resource": "resource A",
    "rowSet": [
        ["A", 1, "test1"],
        ["B", 2, "test2"]
    ],
},
{
    "resource": "resource B",
    "rowSet": [
        ["2/28/2022", 1, 4, "loss"],
        ["3/28/2022", 2, 3, "win"]
    ],
}]

Parsing it as a string results in a parsing error. Setting the type to [AnyObject] doesn't build as it doesn't conform to Decodable

How should this be parsed?

答案1

得分: 2

由于“resource”键确定了“rowSet”键中的数据含义,我建议将其建模为一个具有关联值的枚举。

首先,为这两种资源创建模型,并添加解码初始化程序,以允许它们从JSON数组中解码。

// 我只实现了Decodable一侧。
// 一旦理解了这个思想,编码部分应该很容易实现
struct ResourceA: Decodable {
    // 不确定这些属性的含义...
    let property1: String
    let property2: Int
    let property3: String
    
    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        property1 = try container.decode(String.self)
        property2 = try container decode(Int.self)
        property3 = try container.decode(String.self)
    }
}

struct ResourceB: Decodable {
    let dateString: String
    let score1: Int
    let score2: Int
    let result: String
    
    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        dateString = try container.decode(String.self) // 我有点懒 - 你可以自己解析成日期 :)
        score1 = try container.decode(Int.self)
        score2 = try container.decode(Int.self)
        result = try container.decode(String.self)
    }
}

然后,将ResultSet更改为具有与资源类型相对应的情况的枚举。在解码初始化程序中,首先解码“resource”键,然后根据它来决定为“rowSet”键解码哪种资源。

enum ResultSet: Decodable {
    // 如果标题可以从资源类型计算出来,不需要它作为关联值 - 只需将其添加为计算属性
    case resourceA([ResourceA], headers: [String])
    case resourceB([ResourceB], headers: [String])
    
    enum CodingKeys: CodingKey {
        case resource
        case headers
        case rowSet
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let resourceName = try container.decode(String.self, forKey: .resource)
        let headers = try container.decode([String].self, forKey: .headers)
        switch resourceName {
        case "resource A":
            self = .resourceA(try container.decode([ResourceA].self, forKey: .rowSet), headers: headers)
        case "resource B":
            self = .resourceB(try container.decode([ResourceB].self, forKey: .rowSet), headers: headers)
        default:
            throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "未知的资源名称 \(resourceName)"))
        }
    }
}

示例用法:

let json = """
[{
    "resource": "resource A",
    "headers": [],
    "rowSet": [
        ["A", 1, "test1"],
        ["B", 2, "test2"]
    ],
},
{
    "resource": "resource B",
    "headers": [],
    "rowSet": [
        ["2/28/2022", 1, 4, "loss"],
        ["3/28/2022", 2, 3, "win"]
    ],
}]
""".data(using: .utf8)!
let decoded = try JSONDecoder().decode([ResultSet].self, from: json)
英文:

Since the "resource" key determine what the data in the "rowSet" key mean, I would model this as a enum with associated values.

First, create models for the two kinds of resources. Add decoding initialisers that allows them to be decoded from JSON arrays.

// I only implemented the Decodable side.
// The Encodable side should be trivial to do once you understand the idea
struct ResourceA: Decodable {
    // not sure what these properties mean...
    let property1: String
    let property2: Int
    let property3: String
    
    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        property1 = try container.decode(String.self)
        property2 = try container.decode(Int.self)
        property3 = try container.decode(String.self)
    }
}

struct ResourceB: Decodable {
    let dateString: String
    let score1: Int
    let score2: Int
    let result: String
    
    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        dateString = try container.decode(String.self) // I'm a bit lazy - you can parse this to a Date on your own :)
        score1 = try container.decode(Int.self)
        score2 = try container.decode(Int.self)
        result = try container.decode(String.self)
    }
}

Then change ResultSet to an enum with cases corresponding to the types of resources. In the decoding initialiser, you first decode the "resource" key, and switch on that to decide which kind of resource to decode for the "rowSet" key.

enum ResultSet: Decodable {
    // if the headers can be computed from the resource type, 
    // you don't need it as an associated value - just add it as a computed property instead
    case resourceA([ResourceA], headers: [String])
    case resourceB([ResourceB], headers: [String])
    
    enum CodingKeys: CodingKey {
        case resource
        case headers
        case rowSet
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let resourceName = try container.decode(String.self, forKey: .resource)
        let headers = try container.decode([String].self, forKey: .headers)
        switch resourceName {
        case "resource A":
            self = .resourceA(try container.decode([ResourceA].self, forKey: .rowSet), headers: headers)
        case "resource B":
            self = .resourceB(try container.decode([ResourceB].self, forKey: .rowSet), headers: headers)
        default:
            throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Unknown resource name \(resourceName)"))
        }
    }
}

Example usage:

let json = """
[{
    "resource": "resource A",
    "headers": [],
    "rowSet": [
        ["A", 1, "test1"],
        ["B", 2, "test2"]
    ],
},
{
    "resource": "resource B",
    "headers": [],
    "rowSet": [
        ["2/28/2022", 1, 4, "loss"],
        ["3/28/2022", 2, 3, "win"]
    ],
}]
""".data(using: .utf8)!
let decoded = try JSONDecoder().decode([ResultSet].self, from: json)

huangapple
  • 本文由 发表于 2023年2月8日 17:20:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/75383561.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定