简体   繁体   English

Swift 自定义解码包括另一个自定义解码 model

[英]Swift custom decoding including another custom decoded model

I am currently building a food app using Yelp Fusion API.我目前正在使用 Yelp Fusion API 构建食品应用程序。 There are two models called Business and BusinessDatail in my project.我的项目中有两个模型,分别称为 Business 和 BusinessDatail。 To list up restaurants in a tableView, I fetch a restaurant list from backend and custom decode it as an array of Business, [Business].为了在 tableView 中列出餐厅,我从后端获取餐厅列表并将其自定义解码为 Business 数组,[Business]。 And then, when a tableViewCell of Business is clicked, I fetch detailed business info and custom decode it as a BusinessDetail.然后,当单击 Business 的 tableViewCell 时,我会获取详细的业务信息并将其自定义解码为 BusinessDetail。

struct Business {
    let id: String
    let name: String
    let rating: Float?
    let price: String?
    let displayPhone: String?
    let imageUrl: URL
    let category: String
    let reviewCount: Int
    let coordinates: Coordinates
    let address: Address
}
struct BusinessDetail {

    let id: String
    let name: String
    let rating: Float?
    let price: String?
    let displayPhone: String?
    let imageUrl: URL
    let category: String
    let reviewCount: Int
    let coordinates: Coordinates
    let address: Address

    let isClaimed: Bool
    let isClosed: Bool
    let url: URL
    let phone: String?

    let photoURLs: [URL]

    var hours: [Hours]?
}

Both Business and BusinessDetail are custom decoded models and they are currently initialised this way. Business 和 BusinessDetail 都是自定义解码模型,它们当前以这种方式初始化。

extension Business: Decodable {
    enum CodingKeys: String, CodingKey {
        case id
        case name
        case rating
        case price
        case displayPhone = "display_phone"
        case imageUrl = "image_url"
        case category = "categories"
        case reviewCount = "review_count"
        case coordinates
        case address = "location"
        enum CategoryCodingKeys: String, CodingKey {
            case title
        }
    }

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

        id = try container.decode(String.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
        rating = try container.decode(Float.self, forKey: .rating)
        price = try container.decodeIfPresent(String.self, forKey: .price)
        displayPhone = try container.decodeIfPresent(String.self, forKey: .displayPhone)
        imageUrl = try container.decode(URL.self, forKey: .imageUrl)

        var categoriesContainer = try container.nestedUnkeyedContainer(forKey: .category)
        var tempCategory = ""

        while !categoriesContainer.isAtEnd {
            let categoryContainer = try categoriesContainer.nestedContainer(keyedBy: CodingKeys.CategoryCodingKeys.self)
            let title = try categoryContainer.decode(String.self, forKey: .title)
            tempCategory += tempCategory == "" ? title: ", \(title)"
        }
        category = tempCategory

        reviewCount = try container.decode(Int.self, forKey: .reviewCount)
        coordinates = try container.decode(Coordinates.self, forKey: .coordinates)
        address = try container.decode(Address.self, forKey: .address)
    }
}
extension PlaceDetail: Decodable {
    enum CodingKeys: String, CodingKey {
        case id
        case name
        case rating
        case price
        case displayPhone = "display_phone"
        case imageUrl = "image_url"
        case category = "categories"
        case reviewCount = "review_count"
        case coordinates
        case address = "location"
        enum CategoryCodingKeys: String, CodingKey {
            case title
        }

        case isClaimed = "is_claimed"
        case isClosed = "is_closed"
        case url
        case phone
        case photoURLs = "photos"
        case hours
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
        rating = try container.decode(Float.self, forKey: .rating)
        price = try container.decodeIfPresent(String.self, forKey: .price)
        displayPhone = try container.decode(String.self, forKey: .displayPhone)
        imageUrl = try container.decode(URL.self, forKey: .imageUrl)

        var categoriesContainer = try container.nestedUnkeyedContainer(forKey: .category)
        var tempCategory = ""
        while !categoriesContainer.isAtEnd {
            let categoryContainer = try categoriesContainer.nestedContainer(keyedBy: CodingKeys.CategoryCodingKeys.self)
            let title = try categoryContainer.decode(String.self, forKey: .title)
            tempCategory += tempCategory == "" ? title: ", \(title)"
        }

        category = tempCategory
        reviewCount = try container.decode(Int.self, forKey: .reviewCount)
        coordinates = try container.decode(Coordinates.self, forKey: .coordinates)
        address = try container.decode(Address.self, forKey: .address)

        isClaimed = try container.decode(Bool.self, forKey: .isClaimed)
        isClosed = try container.decode(Bool.self, forKey: .isClosed)
        url = try container.decode(URL.self, forKey: .url)
        phone = try container.decode(String.self, forKey: .phone)
        photoURLs = try container.decode([URL].self, forKey: .photoURLs)
        hours = try container.decodeIfPresent([Hours].self, forKey: .hours)
    }
}

Here is the json for a business list.这是商业清单的 json。

{
    "businesses": [
        {
            "id": "I1D8NHvMWf8oMYceTyLlHg",
            "name": "John Mills Himself",
            "image_url": "https://s3-media3.fl.yelpcdn.com/bphoto/OH84e6eP8zpzBECF0WvTXA/o.jpg",
            "is_closed": false,
            "url": "https://www.yelp.com/biz/john-mills-himself-brisbane?adjust_creative=0mCaOEYvfM9_oOaXgMuW6A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=0mCaOEYvfM9_oOaXgMuW6A",
            "review_count": 55,
            "categories": [
                {
                    "alias": "coffee",
                    "title": "Coffee & Tea"
                },
                {
                    "alias": "bakeries",
                    "title": "Bakeries"
                },
                {
                    "alias": "wine_bars",
                    "title": "Wine Bars"
                }
            ],
            "rating": 4.5,
            "coordinates": {
                "latitude": -27.47151,
                "longitude": 153.025654
            },
            "transactions": [],
            "price": "$",
            "location": {
                "address1": "40 Charlotte St",
                "address2": "",
                "address3": "",
                "city": "Brisbane",
                "zip_code": "4000",
                "country": "AU",
                "state": "QLD",
                "display_address": [
                    "40 Charlotte St",
                    "Brisbane Queensland 4000",
                    "Australia"
                ]
            },
            "phone": "",
            "display_phone": "",
            "distance": 383.2254392716822
        },
        {
            "id": "KaIoCOg-IZJtLdN39Cw__Q",
            "alias": "strauss-brisbane",
            "name": "Strauss",
            "image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/eYKx68uOaEY5k9Jt4TrQPw/o.jpg",
            "is_closed": false,
            "url": "https://www.yelp.com/biz/strauss-brisbane?adjust_creative=0mCaOEYvfM9_oOaXgMuW6A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=0mCaOEYvfM9_oOaXgMuW6A",
            "review_count": 33,
            "categories": [
                {
                    "alias": "cafes",
                    "title": "Cafes"
                }
            ],
            "rating": 4.5,
            "coordinates": {
                "latitude": -27.469804,
                "longitude": 153.027384
            },
            "transactions": [],
            "price": "$",
            "location": {
                "address1": "189 Elizabeth St",
                "address2": "",
                "address3": "",
                "city": "Brisbane",
                "zip_code": "4000",
                "country": "AU",
                "state": "QLD",
                "display_address": [
                    "189 Elizabeth St",
                    "Brisbane Queensland 4000",
                    "Australia"
                ]
            },
            "phone": "+61732365232",
            "display_phone": "+61 7 3236 5232",
            "distance": 247.3760157828213
        }
}

And this is json response for individual BusinessDetail.这是针对单个 BusinessDetail 的 json 响应。

{
    "id": "I1D8NHvMWf8oMYceTyLlHg",
    "name": "John Mills Himself",
    "image_url": "https://s3-media3.fl.yelpcdn.com/bphoto/OH84e6eP8zpzBECF0WvTXA/o.jpg",
    "is_claimed": false,
    "is_closed": false,
    "url": "https://www.yelp.com/biz/john-mills-himself-brisbane?adjust_creative=0mCaOEYvfM9_oOaXgMuW6A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_lookup&utm_source=0mCaOEYvfM9_oOaXgMuW6A",
    "phone": "",
    "display_phone": "",
    "review_count": 55,
    "categories": [
        {
            "alias": "coffee",
            "title": "Coffee & Tea"
        },
        {
            "alias": "bakeries",
            "title": "Bakeries"
        },
        {
            "alias": "wine_bars",
            "title": "Wine Bars"
        }
    ],
    "rating": 4.5,
    "location": {
        "address1": "40 Charlotte St",
        "address2": "",
        "address3": "",
        "city": "Brisbane",
        "zip_code": "4000",
        "country": "AU",
        "state": "QLD",
        "display_address": [
            "40 Charlotte St",
            "Brisbane Queensland 4000",
            "Australia"
        ],
        "cross_streets": ""
    },
    "coordinates": {
        "latitude": -27.47151,
        "longitude": 153.025654
    },
    "photos": [
        "https://s3-media3.fl.yelpcdn.com/bphoto/OH84e6eP8zpzBECF0WvTXA/o.jpg",
        "https://s3-media1.fl.yelpcdn.com/bphoto/L8dJYv1GCW1m5IvD0M3qXw/o.jpg",
        "https://s3-media4.fl.yelpcdn.com/bphoto/oUH4cJmPRuAs_jv_xRtXQg/o.jpg"
    ],
    "price": "$",
    "hours": [
        {
            "open": [
                {
                    "is_overnight": false,
                    "start": "0630",
                    "end": "1530",
                    "day": 0
                },
                {
                    "is_overnight": false,
                    "start": "0630",
                    "end": "1530",
                    "day": 1
                },
                {
                    "is_overnight": false,
                    "start": "1600",
                    "end": "2100",
                    "day": 1
                },
                {
                    "is_overnight": false,
                    "start": "0630",
                    "end": "1530",
                    "day": 2
                },
                {
                    "is_overnight": false,
                    "start": "1600",
                    "end": "2100",
                    "day": 2
                },
                {
                    "is_overnight": false,
                    "start": "0630",
                    "end": "1530",
                    "day": 3
                },
                {
                    "is_overnight": false,
                    "start": "1600",
                    "end": "2330",
                    "day": 3
                },
                {
                    "is_overnight": false,
                    "start": "0630",
                    "end": "1530",
                    "day": 4
                },
                {
                    "is_overnight": false,
                    "start": "1600",
                    "end": "2330",
                    "day": 4
                },
                {
                    "is_overnight": false,
                    "start": "1600",
                    "end": "2330",
                    "day": 5
                }
            ],
            "hours_type": "REGULAR",
            "is_open_now": true
        }
    ]
}

As you can see, BusinessDetail has exactly same info as Business at the top and I'd like to change BusinessDetail like below.如您所见,BusinessDetail 的信息与顶部的 Business 完全相同,我想更改 BusinessDetail 如下所示。

struct BusinessDetail {
    let business: Business

    let isClaimed: Bool
    let isClosed: Bool
    let url: URL
    let phone: String?

    let photoURLs: [URL]

    var hours: [Hours]?
}

However, I am not sure if it is technically possible.但是,我不确定这在技术上是否可行。

Here's a conceptual example, but you can apply the same principle to your case, with the following JSON:这是一个概念示例,但您可以将相同的原理应用于您的案例,使用以下 JSON:

{ "a": 1, "b": 2, "c": "three" }

and these models:这些模型:

struct Smaller: Decodable {
  var a, b: Int
}

struct Larger: Decodable {
  var smaller: Smaller
  var c: String
}

Decoding Smaller doesn't need any extra work (ie no need to manually implement the init(from: Decoder) initializer):解码Smaller不需要任何额外的工作(即无需手动实现init(from: Decoder)初始化程序):

let decoder = JSONDecoder()
let small = try! decoder.decode(Smaller.self, from: jsonData)

But with Larger , you need to manually do this:但是对于Larger ,您需要手动执行此操作:

struct Larger: Decodable {
  var smaller: Smaller
  var c: String

  enum CodingKeys: CodingKey {
     case c
  }

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

     self.smaller = try Smaller.init(from: decoder)
     self.c = try container.decode(String.self, forKey: .c)
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM