Swift JSON парсинг массива словарей - PullRequest
0 голосов
/ 21 февраля 2020

Я пытаюсь разобрать этот JSON файл, который возвращает массив словарей. В основном мне нужно получить значения словарей 'barcode_number' и 'images' для использования их в моем проекте приложения. Файл JSON выглядит следующим образом:

{
    "products": [
        {
            "barcode_number": "4009900360937",
            "barcode_type": "EAN",
            "barcode_formats": "EAN 4009900360937",
            "images": [
                "https://images.barcodelookup.com/4391/43918443-1.jpg"
            ],
            "stores": [],
        }
    ]
}

Код, который я нашел на YouTube до сих пор, чтобы попытаться получить данные, выглядит следующим образом:

    struct Products: Decodable {

    let products: [[String: String]]
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let jsonUrlString = "https://api.barcodelookup.com/v2/products?barcode=4009900360937&formatted=y&key=189y1j3eq5ttwjvzvn01vjflwjgn5u"

        guard let url = URL(string: jsonUrlString) else { return } 
        URLSession.shared.dataTask(with: url) { (data, response, err) in

            guard let data = data else { return }

            do {
                let product = try JSONDecoder().decode(Products.self, from: data)
                print(product.products["barcode_number"]!) 

            } catch let jsonErr {
                print("Error printing json data", jsonErr)
            }

        }.resume()
    }
}

Однако, если я запускаю код выдаётся следующая ошибка

Error printing json data typeMismatch(Swift.Dictionary<Swift.String, Swift.String>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "products", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, String> but found an array instead.", underlyingError: nil))

Ответы [ 2 ]

1 голос
/ 21 февраля 2020

Причина:

products в вышеприведенном JSON не относится к типу [[String: String]]. Вместо этого он имеет тип [[String: Any]]. Вот почему синтаксический анализ выдает ошибку.

Решение:

Вам необходимо создать отдельную модель Product для анализа products array, т. Е.

struct Products: Codable {
    let products: [Product]?
}

struct Product: Codable {
    let barcodeNumber, barcodeType, barcodeFormats: String?
    let images: [String]?
}

Разобрать JSON data как,

do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let response = try decoder.decode(Products.self, from: data)
    print(response.products?.first?.barcodeNumber)
} catch {
    print(error)
}
0 голосов
/ 21 февраля 2020

попробуйте это:

да, похоже, много работы, но это не так.

Как я это сделал?

Легко:

1) введите свой URL в веб-браузере и скопируйте результат

2) go в https://app.quicktype.io/ и введите копию с 1)

3.) скопируйте код справа в ваш проект xcode -> ready;)

-> и у вас есть доступ ко всем переменным - напечатайте safe! -> и лучше всего: вам даже не нужно думать, является ли это словарь или массив, инструмент сделает все это за вас! ;)

  class ViewController: UIViewController {

   struct Welcome: Codable {
       let products: [Product]
   }

   // MARK: - Product
   struct Product: Codable {
       let barcodeNumber, barcodeType, barcodeFormats, mpn: String
       let model, asin, productName, title: String
       let category, manufacturer, brand, label: String
       let author, publisher, artist, actor: String
       let director, studio, genre, audienceRating: String
       let ingredients, nutritionFacts, color, format: String
       let packageQuantity, size, length, width: String
       let height, weight, releaseDate, productDescription: String
       let features: [JSONAny]
       let images: [String]
       let stores, reviews: [JSONAny]

       enum CodingKeys: String, CodingKey {
           case barcodeNumber = "barcode_number"
           case barcodeType = "barcode_type"
           case barcodeFormats = "barcode_formats"
           case mpn, model, asin
           case productName = "product_name"
           case title, category, manufacturer, brand, label, author, publisher, artist, actor, director, studio, genre
           case audienceRating = "audience_rating"
           case ingredients
           case nutritionFacts = "nutrition_facts"
           case color, format
           case packageQuantity = "package_quantity"
           case size, length, width, height, weight
           case releaseDate = "release_date"
           case productDescription = "description"
           case features, images, stores, reviews
       }
   }
    class JSONNull: Codable, Hashable {

        public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
            return true
        }

        public var hashValue: Int {
            return 0
        }

        public init() {}

        public required init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if !container.decodeNil() {
                throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
            }
        }

        public func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            try container.encodeNil()
        }
    }

    class JSONCodingKey: CodingKey {
        let key: String

        required init?(intValue: Int) {
            return nil
        }

        required init?(stringValue: String) {
            key = stringValue
        }

        var intValue: Int? {
            return nil
        }

        var stringValue: String {
            return key
        }
    }

    class JSONAny: Codable {

        let value: Any

        static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
            let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
            return DecodingError.typeMismatch(JSONAny.self, context)
        }

        static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
            let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
            return EncodingError.invalidValue(value, context)
        }

        static func decode(from container: SingleValueDecodingContainer) throws -> Any {
            if let value = try? container.decode(Bool.self) {
                return value
            }
            if let value = try? container.decode(Int64.self) {
                return value
            }
            if let value = try? container.decode(Double.self) {
                return value
            }
            if let value = try? container.decode(String.self) {
                return value
            }
            if container.decodeNil() {
                return JSONNull()
            }
            throw decodingError(forCodingPath: container.codingPath)
        }

        static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
            if let value = try? container.decode(Bool.self) {
                return value
            }
            if let value = try? container.decode(Int64.self) {
                return value
            }
            if let value = try? container.decode(Double.self) {
                return value
            }
            if let value = try? container.decode(String.self) {
                return value
            }
            if let value = try? container.decodeNil() {
                if value {
                    return JSONNull()
                }
            }
            if var container = try? container.nestedUnkeyedContainer() {
                return try decodeArray(from: &container)
            }
            if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
                return try decodeDictionary(from: &container)
            }
            throw decodingError(forCodingPath: container.codingPath)
        }

        static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any {
            if let value = try? container.decode(Bool.self, forKey: key) {
                return value
            }
            if let value = try? container.decode(Int64.self, forKey: key) {
                return value
            }
            if let value = try? container.decode(Double.self, forKey: key) {
                return value
            }
            if let value = try? container.decode(String.self, forKey: key) {
                return value
            }
            if let value = try? container.decodeNil(forKey: key) {
                if value {
                    return JSONNull()
                }
            }
            if var container = try? container.nestedUnkeyedContainer(forKey: key) {
                return try decodeArray(from: &container)
            }
            if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
                return try decodeDictionary(from: &container)
            }
            throw decodingError(forCodingPath: container.codingPath)
        }

        static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
            var arr: [Any] = []
            while !container.isAtEnd {
                let value = try decode(from: &container)
                arr.append(value)
            }
            return arr
        }

        static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
            var dict = [String: Any]()
            for key in container.allKeys {
                let value = try decode(from: &container, forKey: key)
                dict[key.stringValue] = value
            }
            return dict
        }

        static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
            for value in array {
                if let value = value as? Bool {
                    try container.encode(value)
                } else if let value = value as? Int64 {
                    try container.encode(value)
                } else if let value = value as? Double {
                    try container.encode(value)
                } else if let value = value as? String {
                    try container.encode(value)
                } else if value is JSONNull {
                    try container.encodeNil()
                } else if let value = value as? [Any] {
                    var container = container.nestedUnkeyedContainer()
                    try encode(to: &container, array: value)
                } else if let value = value as? [String: Any] {
                    var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
                    try encode(to: &container, dictionary: value)
                } else {
                    throw encodingError(forValue: value, codingPath: container.codingPath)
                }
            }
        }

        static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
            for (key, value) in dictionary {
                let key = JSONCodingKey(stringValue: key)!
                if let value = value as? Bool {
                    try container.encode(value, forKey: key)
                } else if let value = value as? Int64 {
                    try container.encode(value, forKey: key)
                } else if let value = value as? Double {
                    try container.encode(value, forKey: key)
                } else if let value = value as? String {
                    try container.encode(value, forKey: key)
                } else if value is JSONNull {
                    try container.encodeNil(forKey: key)
                } else if let value = value as? [Any] {
                    var container = container.nestedUnkeyedContainer(forKey: key)
                    try encode(to: &container, array: value)
                } else if let value = value as? [String: Any] {
                    var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
                    try encode(to: &container, dictionary: value)
                } else {
                    throw encodingError(forValue: value, codingPath: container.codingPath)
                }
            }
        }

        static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
            if let value = value as? Bool {
                try container.encode(value)
            } else if let value = value as? Int64 {
                try container.encode(value)
            } else if let value = value as? Double {
                try container.encode(value)
            } else if let value = value as? String {
                try container.encode(value)
            } else if value is JSONNull {
                try container.encodeNil()
            } else {
                throw encodingError(forValue: value, codingPath: container.codingPath)
            }
        }

        public required init(from decoder: Decoder) throws {
            if var arrayContainer = try? decoder.unkeyedContainer() {
                self.value = try JSONAny.decodeArray(from: &arrayContainer)
            } else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
                self.value = try JSONAny.decodeDictionary(from: &container)
            } else {
                let container = try decoder.singleValueContainer()
                self.value = try JSONAny.decode(from: container)
            }
        }

        public func encode(to encoder: Encoder) throws {
            if let arr = self.value as? [Any] {
                var container = encoder.unkeyedContainer()
                try JSONAny.encode(to: &container, array: arr)
            } else if let dict = self.value as? [String: Any] {
                var container = encoder.container(keyedBy: JSONCodingKey.self)
                try JSONAny.encode(to: &container, dictionary: dict)
            } else {
                var container = encoder.singleValueContainer()
                try JSONAny.encode(to: &container, value: self.value)
            }
        }
    }



    override func viewDidLoad() {
        super.viewDidLoad()

        struct Products: Codable {
            let products: [Product]
        }

        struct Product: Codable {
            let barcodeNumber, barcodeType, barcodeFormats: String
            let images: [String]
        }




        let jsonUrlString = "https://api.barcodelookup.com/v2/products?barcode=4009900360937&formatted=y&key=189y1j3eq5ttwjvzvn01vjflwjgn5u"

        guard let url = URL(string: jsonUrlString) else { return }
        URLSession.shared.dataTask(with: url) { (data, response, err) in

            guard let data = data else { return }

            do {
                let welcome = try JSONDecoder().decode(Welcome.self, from: data)
                let products = welcome.products
                let product = products[0]
                print(product.barcodeNumber)

            } catch let jsonErr {
                print("Error printing json data", jsonErr)
            }

        }.resume()
    }

}
...