1

How to parse local JSON data where nested (optional) property is same as main.

Items data may be available or may not be available.

struct Category: Identifiable, Codable {
    let id: Int
    let name: String
    let image: String
    var items: [Category]?   
}

I am using common Bundle extension to parse JSON data.

extension Bundle {

    func decode<T: Codable>(_ file: String) -> T {
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle.")
        }

        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle.")
        }

        let decoder = JSONDecoder()

        let formatter = DateFormatter()
        formatter.dateFormat = "y-MM-dd"
        decoder.dateDecodingStrategy = .formatted(formatter)
    
        guard let loaded = try? decoder.decode(T.self, from: data) else {
            fatalError("Failed to decode \(file) from bundle.")
        }
        return loaded
    }
}

For eg data :

[
    {
        "id": 1,
        "name": "Apple",
        "image": "img_url",
        "items" : [
            {
                "id": 1,
                "name": "iPhone",
                "image": "img_url",
                "items" : [
                    {
                        "id": 1,
                        "name": "iPhone 11 Pro",
                        "image": "img_url"
                    },
                    {
                        "id": 2,
                        "name": "iPhone 11 Pro Max",
                        "image": "img_url"
                    }    
                ]
            },
            {
                "id": 2,
                "name": "iPad",
                "image": "img_url",
                "items" : [
                    {
                        "id": 1,
                        "name": "iPad mini",
                        "image": "img_url"
                    },
                    {
                        "id": 2,
                        "name": "iPad Air",
                        "image": "img_url"
                    },
                    {
                        "id": 3,
                        "name": "iPad Pro",
                        "image": "img_url"
                    }
                ]
            }
        ]
    },
    {
        "id": 2,
        "name": "Samsung",
        "image": "img_url",
        "items" : [
            {
                "id": 1,
                "name": "Phone",
                "image": "img_url"
            },
            {
                "id": 2,
                "name": "Tablet",
                "image": "img_url"
            }
        ]
    }
]

1 Answer 1

3

Nesting is not the issue here, You are facing an Array of Contents. so you should pass [Content] to the decoder like:

let jsonDecoder = JSONDecoder()
try! jsonDecoder.decode([Category].self, from: json)

🎁 Property Wrapper

You can implement a simple property wrapper for loading and decoding all of your properties:

@propertyWrapper struct BundleFile<DataType: Decodable> {
    let name: String
    let type: String = "json"
    let fileManager: FileManager = .default
    let bundle: Bundle = .main
    let decoder = JSONDecoder()

    var wrappedValue: DataType {
        guard let path = bundle.path(forResource: name, ofType: type) else { fatalError("Resource not found") }
        guard let data = fileManager.contents(atPath: path) else { fatalError("File not loaded") }
        return try! decoder.decode(DataType.self, from: data)
    }
}

Now you can have any property that should be loaded from a file in a Bundle like:

@BundleFile(name: "MyFile")
var contents: [Content]

Note that since the property should be loaded from the bundle, I raised a FatalError. Because the only person should be responsible for these errors is the developer at the code time (not the run time).

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you so much. I was wasted almost 2 days to solve. @propertyWrapper is like magic...! Thank you 🙏🏻
I am using this response in nested expand/collapse list which is available in iOS 14 beta 3. eg. hackingwithswift.com/quick-start/swiftui/… by Paul Hudson. List behaviour is different. Can you help me out for that?
Yes I can, but not in the comments section of this unrelated question. Maybe you should workaround it and of the issue persists, ask a new question about that SwiftUI's List ;)
It because of id. If id is same for nested items, collapse that all same id. Fixed by changing to unique id, nested also.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.