解码包含以类型属性为键的变量类型数组的JSON对象,并迅速将其转换为具有关联值的枚举5

问题描述

有人知道一种将这样的JSON结构解码为结构的方法吗?

{"included": [
        {
            "type": "breeds","id": "104","attributes": {
                "name": "Boxer"
            },"links": {
                "self": "/public/animals/breeds/104"
            }
        },{
            "type": "colors","id": "27","attributes": {
                "name": "Brindle with White"
            },"links": {
                "self": "/public/animals/colors/27"
            }
        },{
            "type": "species","id": "8","attributes": {
                "singular": "Dog","plural": "Dogs","youngSingular": "puppy","youngPlural": "Puppies"
            },"links": {
                "self": "/public/animals/species/8"
            }
        }
     ]
}

最困难的部分是处理属性属性,该属性可以是许多不同的枚举值,这些枚举值具有不同的关联值,具体取决于将type属性设置为何种值。我想将属性属性存储为具有以下定义的关联值的枚举:

enum IncludedAttributes: Decodable {
    case breeds(name: String)
    case colors(name: String)
    case species(singular: String,plural: String,youngSingular: String,youngPlural: String)
    case statues(name: String,description: String)
    case locations(street: String,city: String,state: String,citystate: String,postalCode: String,country: String,phone: String,lat: String,lon: String,coordinates: String)
    case orgs(name: String,street: String,postalcode: String,email: String,url: String,facebookUrl: String,services: String,type: String,coordinates: String,citystate: String)
    case pictures(orginal: picture,large: picture,small: picture,order: Int,created: String,updated: String)
    case unkNown
}

我需要查看type属性并创建适当的属性枚举。有谁请知道如何在Swift 5中做到这一点?

解决方法

我会使用一组单个结构来组织IncludedAttributes来进行不同的组织。最终结果几乎相同,但是使用struct,您可以利用编译器生成用于解码相关字段的样板。

由于Swift编译器不知道Decodabletype之间的关系,因此基本上所有内容归结为为attributes协议提供自定义实现以处理各种属性。字段。

首先,您可以列出所有已知的项目类型,即:

enum ItemType: String,Decodable {
    case breeds,colors,species
}

然后,您可以定义AnyItem类型,其中可以使用通用attributes代替Decodable,因为此基础类型是在运行时获取的。

struct AnyItem: Decodable {
    var type: ItemType
    var id: String
    var attributes: Decodable

    private enum CodingKeys: String,CodingKey {
        case type,id,attributes
    }
}

可以用符合struct的普通旧Decodable来表示属性。 Swift编译器会自动为它们生成初始化程序。

struct BreedAttributes: Decodable {
    let name: String
}

struct ColorAttributes: Decodable {
    let name: String
}

struct SpecieAttributes: Decodable {
    let singular: String
    let plural: String
    let youngSingular: String
    let youngPlural: String
}

然后为AnyItem实现一个自定义的初始化程序,这在大多数情况下都是微不足道的,除了attributes,您必须根据输入type进行处理。

extension AnyItem {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.type = try container.decode(ItemType.self,forKey: .type)
        self.id = try container.decode(String.self,forKey: .id)

        switch self.type {
        case .breeds:
            self.attributes = try container.decode(BreedAttributes.self,forKey: .attributes)
        case .colors:
            self.attributes = try container.decode(ColorAttributes.self,forKey: .attributes)
        case .species:
            self.attributes = try container.decode(SpecieAttributes.self,forKey: .attributes)
        }
    }
}

并且为了从外部访问正确的attributes,您必须将属性动态转换为期望的类型,即:

struct Response: Decodable {
    let included: [AnyItem]
}

let json = "<YOUR JSON>"
let response = try JSONDecoder().decode(Response.self,from: json.data(using: .utf8)!)

for item in response.included {
    switch item.type {
    case .breeds:
        if let attributes = item.attributes as? BreedAttributes {
            print("Got color attributes: \(attributes)")
        }

    case .colors:
        if let attributes = item.attributes as? ColorAttributes {
            print("Got color attributes: \(attributes)")
        }

    case .species:
        if let attributes = item.attributes as? SpecieAttributes {
            print("Got specie attributes: \(attributes)")
        }
    }
}

最后但并非最不重要的一点是,如果您需要将相同的数据编码回原始层次结构,请对所有结构实施Encodable协议。

完整的游乐场代码: https://gist.github.com/pronebird/64334212b55db2dd3076a634208bbdad