如何解析 Swift 中的本地 JSON 数据?

How to parse local JSON data in Swift?

如何解析本地JSON数据where nested (optional) 属性 is same as main.

项目数据可能可用也可能不可用。

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

我正在使用通用的 Bundle 扩展来解析 JSON 数据。

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
    }
}

例如数据:

[
    {
        "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"
            }
        ]
    }
]

嵌套不是这里的问题,您面对的是 ArrayContent。所以你应该将 [Content] 传递给 decoder,例如:

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

属性 包装器

您可以实现一个简单的 属性 包装器来加载和解码您的所有属性:

@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)
    }
}

现在您可以从 Bundle 中的文件加载任何 属性,例如:

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

注意,由于属性应该从bundle中加载,所以我提出了一个FatalError。因为唯一应该对这些错误负责的人是代码时间的开发人员(而不是 运行 时间)。