This is my first time working with JSON in Swift and when i'm trying to parse the file with my model, this error appears:
The given data was not valid JSON.
I think the problem lies with how I do my model.
The Way I parse the JSON:
import SwiftUI
struct EmergencyView: View {
let emergency: [EmergencyNumberModel]
init() {
let url = Bundle.main.url(forResource: "emergency",
withExtension: "json")!
let data = try! Data(contentsOf: url)
emergency = try! JSONDecoder().decode([EmergencyNumberModel].self, from: data) //Error
}
var body: some View {
List(emergency, id: \.id) { emer in
if emer.Country != nil {
Label(emer.Country, systemImage: "quote.bubble")
.font(.headline)
} else{
Text(emer.Country)
}
}
navigationTitle("Emergency")
}
}
This is a fraction of the JSON i'm using, "emergency.json":
[
{
"Country": {
"Name": "Afghanistan",
"ISOCode": "AF",
"ISONumeric": "4"
},
"Ambulance": {
"All": [
"112"
]
},
"Fire": {
"All": [
"119"
]
},
"Police": {
"All": [
"119"
]
},
"Dispatch": {
"All": [
null
]
},
"Member_112": false,
"LocalOnly": true,
"Notes": false
},
.
.
.
]
This is my Model, "EmergencyNumberModel.swift":
struct EmergencyNumberModel: Codable, Identifiable {
var id = UUID()
let Country: String
let Ambulance: String
let Fire: String
let Police: String
let Dispatch: String
}
Do I need other variables in my model to access the inner keys or the data types of the variables are wrong?
Using https://app.quicktype.io/, to generate the swift strutures,
this is one basic approach to use your json data:
struct EmergencyView: View {
#State var emergencies: [EmergencyModel] = [] // <--- here
var body: some View {
List(emergencies) { emer in
if emer.country.name.isEmpty {
Text("no country name")
} else {
Label(emer.country.name, systemImage: "quote.bubble").font(.headline)
}
}
.onAppear {
if let emrgncy = loadData(from: "emergency") { // <--- here
emergencies = emrgncy
}
}
}
func loadData(from file: String) -> [EmergencyModel]? {
do {
if let filePath = Bundle.main.path(forResource: file, ofType: "json") {
let data = try Data(contentsOf: URL(fileURLWithPath: filePath))
let results = try JSONDecoder().decode([EmergencyModel].self, from: data)
return results
}
} catch {
print("----> error: \(error)") // <-- todo, deal with errors
}
return nil
}
}
struct EmergencyModel: Identifiable, Codable {
let id = UUID() // <--- here
let country: Country
let ambulance, fire, police: Ambulance
let dispatch: Dispatch
let member112, localOnly, notes: Bool
enum CodingKeys: String, CodingKey {
case country = "Country"
case ambulance = "Ambulance"
case fire = "Fire"
case police = "Police"
case dispatch = "Dispatch"
case member112 = "Member_112"
case localOnly = "LocalOnly"
case notes = "Notes"
}
}
struct Ambulance: Codable {
let all: [String]
enum CodingKeys: String, CodingKey {
case all = "All"
}
}
struct Country: Codable {
let name, isoCode, isoNumeric: String
enum CodingKeys: String, CodingKey {
case name = "Name"
case isoCode = "ISOCode"
case isoNumeric = "ISONumeric"
}
}
struct Dispatch: Codable {
let all: [JSONNull?]
enum CodingKeys: String, CodingKey {
case all = "All"
}
}
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
func hash(into hasher: inout Hasher) {
hasher.combine(0)
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
Generic version:
/// Reads a JSON file into a Model object of type T
class JsonReader<T> where T: Decodable {
static func loadData(from file: String) -> T? {
do {
if let filePath = Bundle.main.path(forResource: file, ofType: "json") {
let data = try Data(contentsOf: URL(fileURLWithPath: filePath))
let results = try JSONDecoder().decode(T.self, from: data)
return results
}
} catch {
print("Error: \(error)")
}
return nil
}
}
How to use:
let model = JsonReader<Model>.loadData(from: "FileName")!
Related
My app consists of a ProductFamily entity with a name attribute and an array of PartDetail dictionaries defined as a one-to-many relationship in CoreData. For each ProductFamily, I can have many PartDetail entities (PartNumbers) but for each PartDetail, it can only be associated with one ProductFamily. My example has 5 ProductFamilies, each with an array of 5 PartDetail dictionaries. I'm struggling to get my JSON decoder correct. It's not importing any data into CoreData. You can clone my sample project here:
https://github.com/jegrasso19/ProductFinder-Test2.git
A sample of my JSON data looks like this:
[
{
"Product Family 1": [
{
"partNumber": "160-9013-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9104-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9105-900",
"orderable": false,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9108-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9109-900",
"orderable": true,
"pnDescription": "Part Number Description"
}
]
},
{
"Product Family 2": [
{
"partNumber": "160-9113-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9114-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9115-900",
"orderable": false,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9116-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9201-900",
"orderable": true,
"pnDescription": "Part Number Description"
}
]
}
]
My ProductFamilyJSON Decoder file and ProductFamilyProperties looks like this:
import Foundation
struct ProductFamilyJSON: Decodable {
// Struct that conforms with CodingKey so we can retrieve the product family name as a key
//
private struct JSONCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
// This is the dictionary that contains the JSON data
// The key is the ProductFamily name, and the value is an array of PartDetailInfo.
//
private(set) var productFamilies = [ProductFamilyProperties]()
init(from decoder: Decoder) throws {
var rootContainer = try decoder.unkeyedContainer()
let nestedProductFamilyContainer = try rootContainer.nestedContainer(keyedBy: JSONCodingKeys.self)
// This is where my code fails. When decoding the JSON file,
// it never goes into the while loop.
var productFamily = try ProductFamilyProperties(from: decoder)
while !rootContainer.isAtEnd {
let productFamilyKey = nestedProductFamilyContainer.allKeys.first!
if var partNumberArrayContainer = try? nestedProductFamilyContainer.nestedUnkeyedContainer(forKey: productFamilyKey) {
var partNumbers = Array<PartDetailInfo>()
while !partNumberArrayContainer.isAtEnd {
if let partNumber = try? partNumberArrayContainer.decode(PartDetailInfo.self) {
partNumbers.append(partNumber)
}
}
productFamily.code = UUID().uuidString
productFamily.name = productFamilyKey.stringValue
productFamily.partNumbers = partNumbers
productFamilies.append(productFamily)
}
}
print(productFamilies)
}
}
import Foundation
struct ProductFamilyProperties : Decodable {
var code: String
var name: String
var partNumbers: Array<PartDetailInfo>
enum CodingKeys: String, CodingKey {
case code
case name
case partNumbers
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let rawCode = try? values.decode(String.self, forKey: .code)
let rawName = try? values.decode(String.self, forKey: .name)
let rawPartNumbers = try? values.decode(Array<PartDetailInfo>.self, forKey: .partNumbers)
guard let code = rawCode,
let name = rawName,
let partNumbers = rawPartNumbers
else {
throw myError.programError("Missing Data from Product Family")
}
self.code = code
self.name = name
self.partNumbers = partNumbers
}
var dictionaryValue: [String: Any] {
[
"code": code,
"name": name,
"partNumbers": partNumbers
]
}
}
In my ProductFamilyJSON file, it seems to quit at defining the productFamily variable, which is based on my ProductFamilyProperties. This is apparently wrong but I don't know what it should be defined as. This is my first iOS app I'm trying to develop and learn from. I've spent a while learning CoreData and I've seen so many examples but very few use NSBatchInsertRequest and everyone seems to do this a little differently. I would appreciate some insight on getting this to work. Thanks.
Here is my CoreDataManager class, which contains the NSBatchInsertRequest for reference.
import Foundation
import CoreData
class CoreDataManager: ObservableObject {
let persistentContainer: NSPersistentContainer
static var shared = CoreDataManager()
var viewContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
private init() {
persistentContainer = NSPersistentContainer(name: "ProductFinderTest")
persistentContainer.loadPersistentStores { (description, error) in
if let error = error {
fatalError("Unable to initialize Core Data \(error)")
}
}
let directories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
print(directories[0])
}
func newTaskContext() -> NSManagedObjectContext {
let taskContext = persistentContainer.newBackgroundContext()
taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
taskContext.undoManager = nil
return taskContext
}
}
extension CoreDataManager {
func fetchProductData() async throws {
guard let url = Bundle.main.url(forResource: "ProductFamilies", withExtension: "json"),
let jsonData = try? Data(contentsOf: url)
else {
throw myError.programError("Failed to receive valid response and/or Product Family data.")
}
do {
let jsonDecoder = JSONDecoder()
// ProductFamilyJSON uses this code
let productFamilyJSON = try jsonDecoder.decode(ProductFamilyJSON.self, from: jsonData)
let productFamilyList = productFamilyJSON.productFamilies
print("Received \(productFamilyList.count) Product records.")
print("Start importing product data to the store...")
try await importProductData(from: productFamilyList)
print("Finished importing product data.")
} catch {
throw myError.programError("Wrong Data Format for Product Families")
}
}
private func importProductData(from productList: [ProductFamilyProperties]) async throws {
guard !productList.isEmpty else { return }
let taskContext = newTaskContext()
taskContext.name = "importProductDataContext"
taskContext.transactionAuthor = "importProductData"
try await taskContext.perform {
let batchInsertRequest = self.productListBatchInsertRequest(with: productList)
if let fetchResult = try? taskContext.execute(batchInsertRequest),
let batchInsertResult = fetchResult as? NSBatchInsertResult,
let success = batchInsertResult.result as? Bool, success {
return
}
else {
throw myError.programError("Failed to execute ProductList batch import request.")
}
}
print("Successfully imported Product data.")
}
private func productListBatchInsertRequest(with productList: [ProductFamilyProperties]) -> NSBatchInsertRequest {
var index = 0
let total = productList.count
let batchInsertRequest = NSBatchInsertRequest(entity: ProductFamily.entity(), dictionaryHandler: { dictionary in
guard index < total else { return true }
dictionary.addEntries(from: productList[index].dictionaryValue)
index += 1
return false
})
return batchInsertRequest
}
func requestProductFamilies() -> NSFetchedResultsController<ProductFamily> {
var fetchedResultsController: NSFetchedResultsController<ProductFamily>!
let request: NSFetchRequest = ProductFamily.fetchProductFamilyRequest()
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
fetchedResultsController = NSFetchedResultsController(fetchRequest: request,
managedObjectContext: viewContext,
sectionNameKeyPath: nil,
cacheName: nil)
try? fetchedResultsController.performFetch()
return fetchedResultsController
}
func deleteProductData() async throws {
let taskContext = self.newTaskContext()
let fetchedResultsController = requestProductFamilies()
try fetchedResultsController.performFetch()
let productFamilies = (fetchedResultsController.fetchedObjects ?? []).map(ProductFamilyViewModel.init)
guard !productFamilies.isEmpty else {
print("ProductFamily database is empty.")
return
}
let objectIDs = productFamilies.map { $0.objectId }
print("Start deleting Product data from the store...")
try await taskContext.perform {
let batchDeleteRequest = NSBatchDeleteRequest(objectIDs: objectIDs)
guard let fetchResult = try? taskContext.execute(batchDeleteRequest),
let batchDeleteResult = fetchResult as? NSBatchDeleteResult,
let success = batchDeleteResult.result as? Bool, success
else {
throw myError.programError("Failed to execute Product Family batch delete request.")
}
}
print("Successfully deleted Product data.")
}
}
The problem was with how I was initializing the productFamily variable. I needed to initialize it with the actual values instead of as an empty variable. I also needed to move the nestedProductFamilyContainer inside the while loop. Here is the correct ProductFamilyJSON decoder file. In addition, I changed the partNumber attribute in my ProductFamily entity from NSSet to Array, which allowed more flexibility.
#vadian - I did remove the init(from:) and CodingKeys from ProductFamilyProperties as you suggested and it works just fine. Thanks for the input.
import Foundation
struct ProductFamilyJSON: Decodable {
// Struct that conforms with CodingKey so we can retrieve the product family name as a key
//
private struct JSONCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
// This is the dictionary that contains the JSON data
// The key is the ProductFamily name, and the value is an array of PartDetailInfo.
//
private(set) var productFamilies = [ProductFamilyProperties]()
init(from decoder: Decoder) throws {
var rootContainer = try decoder.unkeyedContainer()
while !rootContainer.isAtEnd {
let nestedProductFamilyContainer = try rootContainer.nestedContainer(keyedBy: JSONCodingKeys.self)
let productFamilyKey = nestedProductFamilyContainer.allKeys.first!
if var partNumberArrayContainer = try? nestedProductFamilyContainer.nestedUnkeyedContainer(forKey: productFamilyKey) {
var partNumbers = Array<PartDetailProperties>()
while !partNumberArrayContainer.isAtEnd {
if let partNumber = try? partNumberArrayContainer.decode(PartDetailProperties.self) {
partNumbers.append(partNumber)
}
}
let partNumbersSorted = partNumbers.sorted(by: { $0.partNumber < $1.partNumber })
let productFamily = ProductFamilyProperties(code: UUID().uuidString, name: productFamilyKey.stringValue, partNumbers: partNumbersSorted)
productFamilies.append(productFamily)
}
}
print(productFamilies)
}
}
I am trying to retrieve JSON data from an API, the JSON I have has no main key JSONFormat
I have tested this with other API's which provide the main key and it is able to retrieve the data from them, is there a different way I should be approaching this? below is the code used to retrieve the API
{ func loadSongs(searchTerm: String, completion: #escaping(([Crime]) -> Void)) {
dataTask?.cancel()
guard let url = buildUrl(forTerm: searchTerm) else {
completion([])
return
}
dataTask = URLSession.shared.dataTask(with: url) { data, _, _ in
guard let data = data else {
completion([])
return
}
if let crimeResponse = try? JSONDecoder().decode(CrimeResponse.self, from: data) {
completion(crimeResponse.crimes)
}
}
dataTask?.resume()
}
private func buildUrl(forTerm searchTerm: String) -> URL? {
guard !searchTerm.isEmpty else { return nil }
let queryItems = [
URLQueryItem(name: "date", value: searchTerm),
]
var components = URLComponents(string: "https://data.police.uk/api/crime-categories")
components?.queryItems = queryItems
return components?.url
}
}
and here are the structs for receiving the data:
struct CrimeResponse: Codable {
let crimes: [Crime]
enum CodingKeys: String, CodingKey {
case crimes
}
}
struct Crime: Codable {
let url: String?
let name : String?
enum CodingKeys: String, CodingKey {
case url = "url"
case name = "name"
}
}
I have a requirement to encode/decode snakeCased JSONs. I found that encoder encodes Value2 object correctly, however decoder fails to decode it. What I do wrong here?
Required Json format:
{
"address_line_1" : "Address",
"full_name" : "Name",
"id" : 2
}
Code:
struct Value1: Codable {
let id: Int
let fullName: String
let addressLine1: String
}
struct Value2: Codable {
let id: Int
let fullName: String
let addressLine_1: String
}
func printJson(_ object: Data) throws {
let json = try JSONSerialization.jsonObject(with: object, options: [])
let data = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted, .sortedKeys])
print(String(data: data, encoding: .utf8)!)
}
func encode<T: Encodable>(_ object: T) throws -> Data {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
return try encoder.encode(object)
}
func decode<T: Decodable>(_ type: T.Type, from data: Data) throws {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
_ = try decoder.decode(type, from: data)
print("✅ Decoded \(type) from:")
try printJson(data)
}
do {
var data: Data
data = try encode(Value1(id: 1, fullName: "Name", addressLine1: "Address"))
try decode(Value1.self, from: data)
data = try encode(Value2(id: 2, fullName: "Name", addressLine_1: "Address"))
_ = try decode(Value1.self, from: data)
_ = try decode(Value2.self, from: data)
} catch {
print("❌ Failed with error:", error)
}
Output:
✅ Decoded Value1 from:
{
"address_line1" : "Address",
"full_name" : "Name",
"id" : 1
}
✅ Decoded Value1 from:
{
"address_line_1" : "Address",
"full_name" : "Name",
"id" : 2
}
❌ Failed with error: keyNotFound(CodingKeys(stringValue: "addressLine_1", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"addressLine_1\", intValue: nil) (\"addressLine_1\"), with divergent representation addressLine1, converted to address_line_1.", underlyingError: nil))
convertFromSnakeCase works correctly and you can can check it in first decode:
_ = try decode(Value1.self, from: data)
After that, when you try to decode the same data but with Value2 type it surely fails as it expects different property name. This is your encoded snake case JSON:
{
"address_line_1" : "Address",
"full_name" : "Name",
"id" : 2
}
After decoder conversion address_line_1 becomes addressLine1 (the same applies to full_name) which fits properties of Value1. If you try to decode the same data for Value2 it fails as property name requires addressLine_1.
In your case, optimal strategy would be to use custom coding keys, like this:
struct Value2: Codable {
private enum Value2CodingKey: String, CodingKey {
case id
case fullName = "full_name"
case addressLine1 = "address_line_1"
}
let id: Int
let fullName: String
let addressLine1: String
}
I found a solution without using custom coding keys, but custom coding strategy instead, so coders handle _ before numbers as well.
So that addressLine1 encodes to address_line_1, and address_line_1 decodes to addressLine1
Usage:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCaseWithNumbers
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCaseWithNumbers
Coder implementation:
extension JSONEncoder.KeyEncodingStrategy {
static var convertToSnakeCaseWithNumbers: JSONEncoder.KeyEncodingStrategy {
.custom { codingKeys -> CodingKey in
let stringValue = codingKeys.last!.stringValue
let newKey = AnyKey(stringValue: convertToSnakeCase(stringValue))!
return newKey
}
}
private static func convertToSnakeCase(_ stringKey: String) -> String {
var key = stringKey
let searchRange = key.index(after: key.startIndex)..<key.endIndex
let nsRange = key.nsRange(from: searchRange)
let matches = NSRegularExpression("([A-Z])|([0-9]+)").matches(in: key, options: [], range: nsRange)
for match in matches.reversed() {
guard let range = key.range(from: match.range) else { continue }
key.insert("_", at: range.lowerBound)
}
return key.lowercased()
}
}
extension JSONDecoder.KeyDecodingStrategy {
static var convertFromSnakeCaseWithNumbers: JSONDecoder.KeyDecodingStrategy {
.custom { (codingKeys) -> CodingKey in
let stringValue = codingKeys.last!.stringValue
let newKey = AnyKey(stringValue: convertFromSnakeCase(stringValue))!
return newKey
}
}
private static func convertFromSnakeCase(_ stringKey: String) -> String {
guard stringKey.contains("_") else {
return stringKey
}
let components = stringKey.split(separator: "_").map({ $0.firstCapitalized })
return components.joined().firstLowercased
}
}
private extension NSRegularExpression {
convenience init(_ pattern: String) {
do {
try self.init(pattern: pattern)
} catch {
preconditionFailure("Illegal regular expression: \(pattern).")
}
}
}
private extension StringProtocol {
var firstLowercased: String { prefix(1).lowercased() + dropFirst() }
var firstCapitalized: String { prefix(1).capitalized + dropFirst() }
}
enum AnyKey: CodingKey {
case string(String)
case int(Int)
var stringValue: String {
switch self {
case .string(let string):
return string
case .int(let int):
return "\(int)"
}
}
var intValue: Int? {
guard case let .int(int) = self else { return nil }
return int
}
init?(stringValue: String) {
guard !stringValue.isEmpty else { return nil }
self = .string(stringValue)
}
init?(intValue: Int) {
self = .int(intValue)
}
}
I'm trying to Parse JSON with code and structure like this:
"custom_attributes": [
{
"attribute_code": "api_attribute",
"value": [
{
"color": [
{
"value_index": "4",
"label": "Red",
"product_super_attribute_id": "1",
"default_label": "Red",
"store_label": "Red",
"use_default_value": true
}
]
},
{
"size": [
{
"value_index": "13",
"label": "35",
"product_super_attribute_id": "2",
"default_label": "35",
"store_label": "35",
"use_default_value": true
}
]
},
I've tried code like this:
Alamofire.request("http://adorableprojects.store/rest/V1/detailProduct/configurable-product").responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let swiftyJsonVar = JSON(responseData.result.value!)
if let resData = swiftyJsonVar["custom_attributes"]["value"]["color"].arrayObject {
self.arrImage = resData as! [[String:AnyObject]]
but I did not get json results at all. when i try if let resData = swiftyJsonVar["custom_attributes"].arrayObject i get all result
custom_attributes , value are arrays
Alamofire.request("http://adorableprojects.store/rest/V1/detailProduct/configurable-product").responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let swiftyJsonVar = JSON(responseData.result.value!).dictionaryValue
if let resData = swiftyJsonVar["custom_attributes"]?.arrayValue , let sec = resData.first?.dictionaryValue["value"]?.arrayValue , let color = sec.first?.dictionaryValue["color"]?.arrayValue {
print("dhjjhdhdsjhdsjdshjdsjhds ",color)
}
else {
}
}
}
Edit : accessing size
Alamofire.request("http://adorableprojects.store/rest/V1/detailProduct/configurable-product").responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let swiftyJsonVar = JSON(responseData.result.value!).dictionaryValue
if let resData = swiftyJsonVar["custom_attributes"]?.arrayValue , let sec = resData.first?.dictionaryValue["value"]?.arrayValue , let color = sec[1].dictionaryValue["size"]?.arrayValue {
print("dhjjhdhdsjhdsjdshjdsjhds ",size)
}
else {
}
}
}
btw recommend
struct Root: Codable {
let customAttributes: [CustomAttribute]
enum CodingKeys: String, CodingKey {
case customAttributes = "custom_attributes"
}
}
struct CustomAttribute: Codable {
let attributeCode: String
let value: [Value]
enum CodingKeys: String, CodingKey {
case attributeCode = "attribute_code"
case value
}
}
struct Value: Codable {
let color: [Color]
}
struct Color: Codable {
let valueIndex, label, productSuperAttributeID, defaultLabel: String
let storeLabel: String
let useDefaultValue: Bool
enum CodingKeys: String, CodingKey {
case valueIndex = "value_index"
case label
case productSuperAttributeID = "product_super_attribute_id"
case defaultLabel = "default_label"
case storeLabel = "store_label"
case useDefaultValue = "use_default_value"
}
}
Instead of manually parsing whole response each time I would suggest you
use to go for much powerful API provided by Apple to us is Codable.
You can read more about codable here: https://developer.apple.com/documentation/swift/codable
You can define coding keys you want to parse and get the ready models from Codable.
Coding Example:
Create your model accordingly
struct Root: Codable {
let customAttributes: [CustomAttribute]
enum CodingKeys: String, CodingKey {
case customAttributes = "custom_attributes"
}
}
struct CustomAttribute: Codable {
let attributeCode: String
let value: [Value]
enum CodingKeys: String, CodingKey {
case attributeCode = "attribute_code"
case value
}
}
struct Value: Codable {
let color: [Color]
}
struct Color: Codable {
let valueIndex, label, productSuperAttributeID, defaultLabel: String
let storeLabel: String
let useDefaultValue: Bool
enum CodingKeys: String, CodingKey {
case valueIndex = "value_index"
case label
case productSuperAttributeID = "product_super_attribute_id"
case defaultLabel = "default_label"
case storeLabel = "store_label"
case useDefaultValue = "use_default_value"
}
}
Usage:
Alamofire.request("http://adorableprojects.store/rest/V1/detailProduct/configurable-product").responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let swiftyJsonVar = JSON(responseData.result.value!)
let customAttributesResponse = swiftyJsonVar["custom_attributes"]
do {
// You can parse response with codable's here
let data = try customAttributesResponse.rawData()
let customAttributes = try JSONDecoder().decode([CustomAttribute].self, from:data)
print(customAttributes)
}
catch {
debugPrint("\(#function)--\(error)")
}
}
}
I am new from Swift programming and I'm having problems with json string parsing. My json string format is as below:
{
"fileName": "test",
"display": "test",
"children": [
{
"fileName": "test2",
"display": "test2",
"children": [
{
"fileName": "test3",
"display": "test3",
"children": [
{
"fileName": "test4",
"display": "test4"
}
]
}
]
}
]
}
I want to parse this to list Dataobject with struct parent and child but until now have no success. My code is as below:
Children model:
import Foundation
public struct Children {
public let fileName: String
public let display: String
public let children: [Children]
public init(lat: String, long: String, hourData: [Children]) {
self.fileName = lat
self.display = long
self.children = hourData
}
}
extension Children: JSONDecodable {
public init(decoder: JSONDecoder) throws {
self.fileName = try decoder.decode(key: "fileName")
self.display = try decoder.decode(key: "display")
self.children = try decoder.decode(key: "children")
}
}
import Foundation
public protocol JSONDecodable {
init(decoder: JSONDecoder) throws
}
public enum JSONDecoderError: Error {
case invalidData
case keyNotFound(String)
case keyPathNotFound(String)
}
public struct JSONDecoder {
typealias JSON = [String: AnyObject]
// MARK: - Properties
private let JSONData: JSON
// MARK: - Static Methods
public static func decode<T: JSONDecodable>(data: Data) throws -> T {
let decoder = try JSONDecoder(data: data)
return try T(decoder: decoder)
}
// MARK: - Initialization
public init(data: Data) throws {
if let JSONData = try JSONSerialization.jsonObject(with: data, options: []) as? JSON {
self.JSONData = JSONData
} else {
throw JSONDecoderError.invalidData
}
}
// MARK: -
private init(JSONData: JSON) {
self.JSONData = JSONData
}
// MARK: - Public Interface
func decode<T>(key: String) throws -> T {
if key.contains(".") {
return try value(forKeyPath: key)
}
guard let value: T = try? value(forKey: key) else { throw JSONDecoderError.keyNotFound(key) }
return value
}
func decode<T: JSONDecodable>(key: String) throws -> [T] {
if key.contains(".") {
return try value(forKeyPath: key)
}
guard let value: [T] = try? value(forKey: key) else { throw JSONDecoderError.keyNotFound(key) }
return value
}
// MARK: - Private Interface
private func value<T>(forKey key: String) throws -> T {
guard let value = JSONData[key] as? T else { throw JSONDecoderError.keyNotFound(key) }
return value
}
private func value<T: JSONDecodable>(forKey key: String) throws -> [T] {
if let value = JSONData[key] as? [JSON] {
return try value.map({ (partial) -> T in
let decoder = JSONDecoder(JSONData: partial)
return try T(decoder: decoder)
})
}
throw JSONDecoderError.keyNotFound(key)
}
// MARK: -
private func value<T>(forKeyPath keyPath: String) throws -> T {
var partial = JSONData
let keys = keyPath.components(separatedBy: ".")
for i in 0..<keys.count {
if i < keys.count - 1 {
if let partialJSONData = JSONData[keys[i]] as? JSON {
partial = partialJSONData
} else {
throw JSONDecoderError.invalidData
}
} else {
return try JSONDecoder(JSONData: partial).value(forKey: keys[i])
}
}
throw JSONDecoderError.keyPathNotFound(keyPath)
}
private func value<T: JSONDecodable>(forKeyPath keyPath: String) throws -> [T] {
var partial = JSONData
let keys = keyPath.components(separatedBy: ".")
for i in 0..<keys.count {
if i < keys.count - 1 {
if let partialJSONData = JSONData[keys[i]] as? JSON {
partial = partialJSONData
} else {
throw JSONDecoderError.invalidData
}
} else {
return try JSONDecoder(JSONData: partial).value(forKey: keys[i])
}
}
throw JSONDecoderError.keyPathNotFound(keyPath)
}
}
Please help me resolve this.