Swift 4 JSON Codable - value returned is sometimes an object, others an array

扶醉桌前 提交于 2019-11-30 05:24:03

问题


the data I'm getting from an API returns a single object but when there's multiple objects, it returns an array in the same key. With the current model (struct) I'm working with, the decoding fails when an array shows up.

These results are randomly ordered, meaning I can't know when I will be served an object or array.


Is there a way to create a model that takes this fact into account and can assign the correct type to cast for the value ('String' or '[String]') so that the decoding continues without problem?


This is an example of when an object is returned:

{
    "firstFloor": {
        "room": "Single Bed"
    }
}

This is an example of when an array is returned (for the same key):

{
    "firstFloor": {
        "room": ["Double Bed", "Coffee Machine", "TV", "Tub"]
    }
}

Example of the struct that should be able to be used as model to decode both samples above:

struct Hotel: Codable {
    let firstFloor: Room

    struct Room: Codable {
        var room: String // the type has to change to either array '[String]' or object 'String' depending on the returned results
    }
}

These results are randomly ordered, meaning I can't know when I will be served an object or array.

Here is the complete playground file:

import Foundation

// JSON with a single object
let jsonObject = """
{
    "firstFloor": {
        "room": "Single Bed"
    }
}
""".data(using: .utf8)!

// JSON with an array instead of a single object
let jsonArray = """
{
    "firstFloor": {
        "room": ["Double Bed", "Coffee Machine", "TV", "Tub"]
    }
}
""".data(using: .utf8)!

// Models
struct Hotel: Codable {
    let firstFloor: Room

    struct Room: Codable {
        var room: String // the type has to change to either array '[String]' or object 'String' depending on the results of the API
    }
}

// Decoding
let decoder = JSONDecoder()
let hotel = try decoder.decode(Hotel.self, from: jsonObject) //

print(hotel)

回答1:


You might encapsulate the ambiguity of the result using an Enum with Associated Values (String and Array in this case), for example:

enum MetadataType: Codable {
    case array([String])
    case string(String)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self = try .array(container.decode(Array.self))
        } catch DecodingError.typeMismatch {
            do {
                self = try .string(container.decode(String.self))
            } catch DecodingError.typeMismatch {
                throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
            }
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .array(let array):
            try container.encode(array)
        case .string(let string):
            try container.encode(string)
        }
    }
}

struct Hotel: Codable {
    let firstFloor: Room

    struct Room: Codable {
        var room: MetadataType
    }
}


来源:https://stackoverflow.com/questions/48739760/swift-4-json-codable-value-returned-is-sometimes-an-object-others-an-array

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!