Search code examples
iosswiftstructjson-serialization

Decode nested JSON arrays and dictionaries in Swift using Structs with JSONserialization


I am trying to create some structs to decode some JSON received from an API using JSONSerialization.jsonObject(with: data, options: [])

This is what the JSON looks like:

{"books":[{"title":"The Fountainhead.","author":"Ayn Ranyd"},{"title":"Tom Sawyer","author":"Mark Twain"},{"title":"Warhol","author":"Blake Gopnik"}]}

Here are the structs that I am trying to use for decoding.

struct BooksReturned : Codable {
        let books : [Book]?
    }
    struct Book : Codable {
        let BookParts: Array<Any>?
    }
    struct BookParts : Codable {
        let titleDict : Dictionary<String>?
        let authorDict : Dictionary<String>?
    }

The error is:

The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.})))

The non-working code I am using to decode is:

let task = session.dataTask(with: url) { data, response, error in
            if let data = data, error == nil {
                let nsdata = NSData(data: data)
                DispatchQueue.main.async {
                    if let str = String(data: data, encoding: .utf8) {

                        let json = try? JSONSerialization.jsonObject(with: data, options: [])                        
                        do {

                            let mybooks = try JSONDecoder().decode(BooksReturned.self, from: data)
                             //do something with book
                            }

                        } catch {
                            print(error.localizedDescription)
                            print(error)
                        }
                    }
                }
            } else {
                // Failure
            }
        }
        task.resume()
    }

I have some very limited ability to change JSON. The only thing I can do is remove the "books" : Everything else is received from an external API.

Thanks for any suggestions on how to get this to work.


Solution

  • The JSON you provided seems to be valid. Modify your Book model and the decoding part as the following.

    Model:

    struct Book: Codable {
        let title, author: String
    }
    

    Decoding:

    let task = session.dataTask(with: url) { data, response, error in
        if let data = data, error == nil {
            DispatchQueue.main.async {
                do {
                    let mybooks = try JSONDecoder().decode(BooksReturned.self, from: data)
                    print(mybooks)
                }
            } catch {
                print(error.localizedDescription)
                print(error)
            }
        }
    } else {
        // Failure
    }
    task.resume()