简体   繁体   English

将 json 数组解码为 swift 中的 object

[英]Decode json array as an object in swift

Im trying to decode this type of json in swift:我试图在 swift 中解码这种类型的 json:

[{"Product":[{"Name":"exampleName"},{"Quantity":"1"}]}]

Therefore I have these structs:因此我有这些结构:

struct DataModel: Codable {
    var product: Product?

    enum CodingKeys: String, CodingKey {
        case product = "Product"
    }
}

struct Product: Codable {
    var name, quantity: String?

    enum CodingKeys: String, CodingKey {
        case name = "Name"
        case quantity = "Quantity"
    }
}

and this function:还有这个 function:

if let data = try? JSONDecoder().decode([DataModel].self, from: jsonData) {
     //continue with result...       
} else {
     print("failed")
}

Basically I want to achieve that my object contains all properties from the array.基本上我想实现我的 object 包含数组中的所有属性。

Thanks in advance =)提前感谢=)

First, I assume you'd really like the final result to be [Product] where Product looks like this:首先,我假设您真的希望最终结果是[Product] ,其中 Product 看起来像这样:

struct Product {
    var name: String = ""
    var quantity: Int = 0
}

So if any keys are missing, they'll get the defaults.因此,如果缺少任何键,它们将获得默认值。 You could change this solution to throw an error in that case, but I think this approach is much nicer than Optionals.在这种情况下,您可以更改此解决方案以引发错误,但我认为这种方法比 Optionals 好得多。

Given that, here's how you decode it:鉴于此,以下是您对其进行解码的方式:

extension Product: Decodable {
    init(from decoder: Decoder) throws {
        // This is a very uniform type (all Strings). Swift can decode that
        // without much help.
        let container = try decoder
            .singleValueContainer()
            .decode([String: [[String: String]]].self)

        // Extract the Products. Throwing an error here might be nicer.
        let keyValues = container["Product"] ?? []

        // This loops over every Key-Value element, and then loops through
        // each one. We only expect one element in the second loop, though.
        for keyValue in keyValues {
            for (key, value) in keyValue {
                switch key {
                case "Name": self.name = value
                case "Quantity": self.quantity = Int(value) ?? 0
                default: break // Ignore unknown keys
                }
            }
        }
        // This solution just assigns defaults for missing keys, but
        // you could validate that everything was found, and throw an
        // error here if desired.
    }
}

let data = try JSONDecoder().decode([Product].self, from: jsonData)
// [{name "exampleName", quantity 1}]

data.first!.name
// "exampleName"

In most cases the above is probably fine, but it's also very sloppy about malformed data.在大多数情况下,上述情况可能很好,但对于格式错误的数据也很草率。 It just returns a default object, which could make an error very hard to track down.它只返回一个默认的 object,这可能会使错误很难追踪。 This example goes in the other direction, and does all the error checking you would expect from a normal Decodable.这个例子是相反的,它会做你期望从一个普通的 Decodable 中得到的所有错误检查。

First, we'll need a CodingKey that can accept any String.首先,我们需要一个可以接受任何字符串的 CodingKey。 I really don't understand why this isn't built into stdlib:我真的不明白为什么这不是stdlib内置的:

struct AnyStringKey: CodingKey, Hashable, ExpressibleByStringLiteral {
    var stringValue: String
    init(stringValue: String) { self.stringValue = stringValue }
    init(_ stringValue: String) { self.init(stringValue: stringValue) }
    var intValue: Int?
    init?(intValue: Int) { return nil }
    init(stringLiteral value: String) { self.init(value) }
}

I also find DecodingErrors very cumbersome to build, so I often build little helper functions:我也发现 DecodingErrors 构建起来非常麻烦,所以我经常构建一些小的辅助函数:

func keyNotFound(_ key: String, codingPath: [CodingKey]) -> Error {
    DecodingError.keyNotFound(AnyStringKey(key),
                              .init(codingPath: [],
                                    debugDescription: "\(key) key not found"))
}


func typeMismatch(_ key: String, expected: Any.Type, codingPath: [CodingKey]) -> Error {
    DecodingError.typeMismatch(expected, .init(codingPath: codingPath + [AnyStringKey(key)],
                                               debugDescription: "Expected \(expected)."))
}

With those in place, here's a more strict decoder:有了这些,这里有一个更严格的解码器:

extension Product: Decodable {
    init(from decoder: Decoder) throws {
        // This is a very uniform type (all Strings). Swift can decode that
        // without much help.
        let container = try decoder
            .singleValueContainer()
            .decode([String: [[String: String]]].self)

        var codingPath: [AnyStringKey] = []

        // Extract the Products. Throwing an error here might be nicer.
        guard let keyValues = container["Product"] else {
            throw keyNotFound("Product", codingPath: codingPath)
        }

        codingPath.append("Product")

        var name: String?
        var quantity: Int?

        // This loops over every Key-Value element, and then loops through
        // each one. We only expect one element in the second loop, though.
        for keyValue in keyValues {
            for (key, value) in keyValue {
                switch key {
                case "Name":
                    name = value

                case "Quantity":
                    guard let intValue = Int(value) else {
                        throw typeMismatch("Quantity",
                                           expected: Int.self,
                                           codingPath: codingPath)
                    }
                    quantity = intValue

                default: break // Ignore unknown keys
                }
            }
        }

        guard let name = name else {
            throw keyNotFound("Name", codingPath: codingPath)
        }
        self.name = name

        guard let quantity = quantity else {
            throw keyNotFound("Quantity", codingPath: codingPath)
        }
        self.quantity = quantity
    }
}

Something is wrong in the statement.声明中有问题。

If a product is defined by a name and a quantity, then it is not an array.. So either the JSON is wrong, either the Data structure.如果一个产品是由名称和数量定义的,那么它不是一个数组。所以要么 JSON 是错误的,要么是数据结构。 Maybe both.也许两者兼而有之。

[{"Product":[{"Name":"exampleName"},{"Quantity":"1"}]}]

Your JSON should be:您的 JSON 应该是:

[{"Name":"exampleName", "Quantity":"1"}]

And your model:还有你的 model:

struct Product: Codable {
    var name, quantity: String

    // This can be omitted if 
    enum CodingKeys: String, CodingKey {
        case name = "Name"
        case quantity = "Quantity"
    }
}

Then:然后:

do {
    var products = try JSONDecoder().decode([Product].self, from: jsonData)
} catch {
    print("Decoding Error : \(error)")
}

Should work perfectly, and fail if data are not complete.应该可以完美运行,如果数据不完整则失败。

See other answers for custom decoding that fills properties even if json data are missing.. I suppose it should not happen.即使 json 数据丢失,请参阅填充属性的自定义解码的其他答案。我想它不应该发生。 If quantity is not provided, then you let your application continue with a meaningless number if you fill it by yourself.如果没有提供数量,那么如果您自己填写,您可以让您的申请继续使用一个无意义的数字。

By the way, I know it is another problem, but we usually not encode quantity in products in this kind of application.. We have the catalog on one side with products name and unique reference, and the stocks on another.顺便说一句,我知道这是另一个问题,但在这种应用程序中,我们通常不会在产品中编码数量。我们的目录一侧是产品名称和唯一参考,另一侧是库存。 It is more flexible to have two different requests: FetchCatalog, and FetchStocks.有两个不同的请求更加灵活:FetchCatalog 和 FetchStocks。

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

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