Search code examples
iosswiftstruct

Swift 5: Decoding Nested JSON


I'm having a little trouble decoding some JSON data into a struct. I've tried below methods and it doesn't work:

JSON:

{
    "submission_date": "2020-02-28T14:21:46.000+08:00",
    "status": "pending",
    "requestor": {
        "name": "Adam"
    },
    "claim_items": [
        {
            "date": "2020-02-20",
            "description": "TV",
            "currency": "MYR",
            "amount": "103.0",
            "amount_in_ringgit": "10.0"
        },
        {
            "date": "2020-02-20",
            "description": "Netflix",
            "currency": "MYR",
            "amount": "12.0",
            "amount_in_ringgit": "10.0"
        }
    ]
}

Struct Method 1:

struct ClaimDetail: Decodable {
    let submission_date: String
    let status: String
    let requestor: Requestor
    let claim_items: [ClaimItem]
}

struct Requestor: Decodable {
    let name: String
    
    init(json: [String:Any]) {
        name = json["name"] as? String ?? ""
    }
}

struct ClaimItem: Decodable {
    let date: String
    let description: String
    let currency: String
    let amount: String
    let amount_in_ringgit: String
    
    init(json: [String:Any]) {
        date = json["date"] as? String ?? ""
        description = json["description"] as? String ?? ""
        currency = json["currency"] as? String ?? ""
        amount = json["amount"] as? String ?? ""
        amount_in_ringgit = json["amount_in_ringgit"] as? String ?? ""
    }
}

Struct Method 2:

struct ClaimDetail: Decodable {
    let submission_date: String
    let status: String
    let requestor: Requestor
    let claim_items: [ClaimItem]
    
    struct Requestor: Decodable {
        let name: String
        
        init(json: [String:Any]) {
            name = json["name"] as? String ?? ""
        }
    }
    
    struct ClaimItem: Decodable {
        let date: String
        let description: String
        let currency: String
        let amount: String
        let amount_in_ringgit: String
        
        init(json: [String:Any]) {
            date = json["date"] as? String ?? ""
            description = json["description"] as? String ?? ""
            currency = json["currency"] as? String ?? ""
            amount = json["amount"] as? String ?? ""
            amount_in_ringgit = json["amount_in_ringgit"] as? String ?? ""
        }
    }
}

Struct Method 3 (via https://app.quicktype.io/):

// MARK: - ClaimDetail
struct ClaimDetail: Codable {
    let submissionDate, status: String
    let requestor: Requestor
    let claimItems: [ClaimItem]

    enum CodingKeys: String, CodingKey {
        case submissionDate = "submission_date"
        case status, requestor
        case claimItems = "claim_items"
    }
}

// MARK: - ClaimItem
struct ClaimItem: Codable {
    let date, claimItemDescription, currency, amount: String
    let amountInRinggit: String

    enum CodingKeys: String, CodingKey {
        case date
        case claimItemDescription = "description"
        case currency, amount
        case amountInRinggit = "amount_in_ringgit"
    }
}

// MARK: - Requestor
struct Requestor: Codable {
    let name: String
}

URL Session

URLSession.shared.dataTask(with: requestAPI) { [weak self] (data, response, error) in
    if let data = data {
        do {
            let json = try JSONDecoder().decode(ClaimDetail.self, from: data)
            print (json)
        } catch let error {
            print("Localized Error: \(error.localizedDescription)")
            print("Error: \(error)")
        }
    }
}.resume()

All returns below error:

Localized Error: The data couldn’t be read because it isn’t in the correct format.

Error: dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))


Solution

  • Solution:

    I used struct method #1 and it wasn't the issue. The issue was with how I decoded the data in URLSession. For some reason, this works:

    URLSession.shared.dataTask(with: requestAPI) { [weak self] (data, response, error) in
        
        if let data = data {
        
            do {
                let dataString = String(data: data, encoding: .utf8)
                let jsondata = dataString?.data(using: .utf8)
                let result = try JSONDecoder().decode(ClaimDetail.self, from: jsondata!)
                print(result)
            
            } catch let error {
                print("Localized Error: \(error.localizedDescription)")
                print("Error: \(error)")
            }
        }
    }.resume()
    

    Screenshot:

    enter image description here

    I don't really understand but I guess I had to convert the data into a string, then decode that instead?

    Thank you all for helping out.