如果它是一个数组并且第一个项目与其他项目的类型不同,我如何在Swift中解码JSON?

时间:2018-05-30 01:44:48

标签: json swift struct jsondecoder

说JSON看起来像这样:

[
    {
        "name": "Spot",
        "breed": "dalmation"
    },
    {
        "color": "green",
        "eats": "lettuce"
    },
    {
        "color": "brown",
        "eats": "spinach"
    },
    {
        "color": "yellow",
        "eats": "cucumbers"
    }
]

从API返回的JSON响应中的第一项始终是狗,而之后的所有项都是乌龟。所以第0项是狗,项目1到N-1是海龟。

如何将其解析为我可以阅读的内容,例如:

struct Result: Codable {
    let dog: Dog
    let turtles: [Turtle]
}

有可能吗?

2 个答案:

答案 0 :(得分:9)

您可以为Result结构实现自定义解码器。

init(from decoder: Decoder) throws {
    var container = try decoder.unkeyedContainer()

    // Assume the first one is a Dog
    self.dog = try container.decode(Dog.self)

    // Assume the rest are Turtle
    var turtles = [Turtle]()

    while !container.isAtEnd {
        let turtle = try container.decode(Turtle.self)
        turtles.append(turtle)
    }

    self.turtles = turtles
}

通过少量工作,您可以支持Dog词典在Turtle词典数组中的任何位置。

由于您声明您的结构是Codable而不仅仅是Decodable,您还应该从Encodable实现自定义encode(to:),但这是留给读者的练习。

答案 1 :(得分:1)

所以你的Array包含两种类型的元素。这是Type1OrType2问题的一个很好的例子。对于此类情况,您可以考虑将enum与关联类型一起使用。对于您的情况,您需要Codable枚举,并自定义init(from:) throws& func encode(to:) throws

enum DogOrTurtle: Codable {
    case dog(Dog)
    case turtle(Turtle)

    struct Dog: Codable {
        let name: String
        let breed: String
    }

    struct Turtle: Codable {
        let color: String
        let eats: String
    }
}

extension DogOrTurtle {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            // First try to decode as a Dog, if this fails then try another
            self = try .dog(container.decode(Dog.self))
        } catch {
            do {
                // Try to decode as a Turtle, if this fails too, you have a type mismatch
                self = try .turtle(container.decode(Turtle.self))
            } catch {
                // throw type mismatch error
                throw DecodingError.typeMismatch(DogOrTurtle.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type, (Dog or Turtle)") )
            }
        }
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .dog(let dog):
            try container.encode(dog)
        case .turtle(let turtle):
            try container.encode(turtle)
        }
    }
}

使用这种方法,您无需担心数组中DogTurtle的排序。元素可以按任何顺序和任何数字出现。

用法:(我故意将狗移到第三个索引处)

let jsonData = """
[
    {
        "color": "green",
        "eats": "lettuce"
    },
    {
        "color": "brown",
        "eats": "spinach"
    },
    {
        "name": "Spot",
        "breed": "dalmation"
    },
    {
        "color": "yellow",
        "eats": "cucumbers"
    }
]
""".data(using: .utf8)!

do {
    let array = try JSONDecoder().decode([DogOrTurtle].self, from: jsonData)
    array.forEach { (dogOrTurtle) in
        switch dogOrTurtle {
        case .dog(let dog):
            print(dog)
        case .turtle(let turtle):
            print(turtle)
        }
    }
} catch {
    print(error)
}