Home > Net >  How to fix? Expected to decode Dictionary<String, Any> but found a string/data instead
How to fix? Expected to decode Dictionary<String, Any> but found a string/data instead

Time:09-10

What is wrong here? Or how else I should decode, I would NOT use JSONSerialize.

let jsonData = try! Data(contentsOf: urls[0])
let decoder = JSONDecoder()
let d = try decoder.decode([String: JSON].self, from: jsonData)

file content is a simple JSON:

{"name": "fff", "price": 10}

And my JSON code:

public enum JSON: Decodable {
    case string(String)
    case number(Float)
    case object([String:JSON])
    case array([JSON])
    case bool(Bool)
}

CodePudding user response:

You need to add a custom init(from:) where you try to decode into each possible enum case until you are successful or throw an error

Here is a short version that handles three of the cases

struct EnumDecoderError: Error {}

public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    if let string = try? container.decode(String.self) {
        self = JSON.string(string)
    } else if let number = try? container.decode(Float.self) {
        self = JSON.number(number)
    } else if let array = try? container.decode([JSON].self) {
        self = JSON.array(array)
    } else {
        throw EnumDecoderError()
    }
}

as mentioned in the comments by @LeoDabus we can catch typeMismatch errors (and throw any other error directly) or as before throw an error at the end if no decoding worked. (Again a shortened version)

public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()

    do {
        let string = try container.decode(String.self)
        self = JSON.string(string)
    } catch DecodingError.typeMismatch {
        do {
            let number = try container.decode(Float.self)
            self = JSON.number(number)
        } catch DecodingError.typeMismatch {
            do {
                let array = try container.decode([JSON].self)
                self = JSON.array(array)
            } catch DecodingError.typeMismatch {
                throw DecodingError.typeMismatch(JSON.self, .init(codingPath: decoder.codingPath, debugDescription: "Data type is not supported"))
            }
        }
    }
}

CodePudding user response:

You need to decode it into a structure

private struct MyData: Codable {
    var name: String?
    var price: Int?
}

...
let jsonData = try! Data(contentsOf: urls[0])
let d = try JSONDecoder().decode(Data.self, from: jsonData)
...

CodePudding user response:

First of all you don't need to maintain datatypes in the JSON enum to parse your response.

The JSONDecoder will be able to parse with the appropriate datatype if you match your object to the response structure that you receive from APIs or JSON files maintained locally

Taking you json file as example:

{"name": "fff", "price": 10}

The recommended way to parse this structure would be as follows

  • Create a struct or class according to your needs. For this I will use a struct

    struct Product: Decodable {
      var name: String?
      var price: Int?
    

    }

    I have marked both the vars optionals just in case to handle the failures if the field does not exist in the JSON response.

  • Parse the Structure

Use the product struct that was created in the previous step by creating a decoder instance and setting the Product.Self to parse the object

let decoder = JSONDecoder()
let productObject = try decoder.decode(Product.self, from: jsonData)

If you have an array of objects of the same structure in the JSON response use below:

let productObjects = try decoder.decode([Product].self, from: jsonData)

Just include [] around the product object

  • Related