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)")
}
}
}
Related
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"
}
}
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")!
So, the issue is - I am trying to display the Mars weather from the Mars Insight API. Here is a link Insight Weather, the data is returning in JSON format and has three levels. The keys have names that change depending on the current date (sols). How to make the structure of mutable properties?... when the property names change every day. Do we have any instruments to parse such JSON?
{
"815": {
"First_UTC": "2021-03-12T14:54:38Z",
"Last_UTC": "2021-03-13T15:34:09Z",
"Month_ordinal": 12,
"Northern_season": "late winter",
"PRE": {
"av": 728.378,
"ct": 153082,
"mn": 708.4211,
"mx": 744.9279
},
"Season": "winter",
"Southern_season": "late summer",
"WD": {
"most_common": null
}
},
"818": {
"First_UTC": "2021-03-15T20:01:49Z",
"Last_UTC": "2021-03-16T17:32:54Z",
"Month_ordinal": 12,
"Northern_season": "late winter",
"PRE": {
"av": 727.696,
"ct": 109855,
"mn": 710.223,
"mx": 743.946
},
"Season": "winter",
"Southern_season": "late summer",
"WD": {
"most_common": null
}
},
"819": {
....
,
"sol_keys": [
"815",
"818",
"819",
"820",
"821"
],
"validity_checks": {
"815": {
"PRE": {
"sol_hours_with_data": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23
],
"valid": true
}
}
So, Structure:
import Foundation
struct WeatherData: Decodable {
let season: String?
let pre: pre?
let solKeys: [String]
enum CodingKeys: String, CodingKey {
case season = "season"
case pre = "PRE"
case solKeys = "sol_keys"
}
struct pre: Decodable {
let av: Double?
let mn: Double?
let mx: Double?
enum CodingKeys: String, CodingKey {
case av = "av"
case mn = "mn"
case mx = "mx"
}
}
}
Parsing:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://api.nasa.gov/insight_weather/?api_key=DEMO_KEY&feedtype=json&ver=1.0"
guard let url = URL(string: urlString) else { return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let safeData = data else { return }
do {
let solWeather = try JSONDecoder().decode([String:WeatherData].self, from: safeData)
if let keys = solWeather["sol_keys"] {
for key in keys { //error: For-in loop requires 'WeatherData' to conform to 'Sequence'
let report = solWeather [key]
}
}
print(solWeather)
}
catch let error {
print(error)
}
}.resume()
}
}
Got an error: For-in loop requires 'WeatherData' to conform to 'Sequence'
Don't understand why :(( Can someone help me?
Thanks!
You cannot decode [String:WeatherData].self because the dictionary contains other values which are not WeatherData, for example the [String] value of sol_keys.
The only way to decode this JSON with JSONDecoder is to implement init(with decoder, decode the sol_keys and create your own temporary CodingKeys to be able to decode the arbitrary dictionary keys.
First declare the custom CodingKey
public struct SolKeys: CodingKey {
public let stringValue: String
public init?(stringValue: String) { self.stringValue = stringValue }
public var intValue: Int? { return nil }
public init?(intValue: Int) { return nil }
}
The Decodable structs are
struct SolData : Decodable {
let firstUTC, lastUTC : Date
let pre : Pre
private enum CodingKeys : String, CodingKey {
case firstUTC = "First_UTC", lastUTC = "Last_UTC", pre = "PRE"
}
}
struct Pre: Decodable {
let av, mn, mx : Double
}
struct WeatherData: Decodable {
let solKeys: [String]
var soldata = [String:SolData]()
enum CodingKeys: String, CodingKey {
case solKeys = "sol_keys"
}
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.solKeys = try container.decode([String].self, forKey: .solKeys)
let customContainer = try decoder.container(keyedBy: SolKeys.self)
for key in solKeys {
let solKey = SolKeys(stringValue: key)!
let data = try customContainer.decode(SolData.self, forKey: solKey)
soldata[key] = data
}
}
}
And the code to receive and decode the data
let urlString = "https://api.nasa.gov/insight_weather/?api_key=DEMO_KEY&feedtype=json&ver=1.0"
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let solWeather = try decoder.decode(WeatherData.self, from: data!)
let keys = solWeather.solKeys
for key in keys {
let report = solWeather.soldata[key]!
print(report)
}
}
catch {
print(error)
}
}.resume()
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 got to know struct "Codable" in swift 4.0, *.
So, I tried that when decode josn.
if let jsonData = jsonString.data(using: .utf8) {
let decodingData = try? JSONDecoder().decode(SampleModel.self, from: jsonData)
}
Example sample data model below.
struct SampleModel : Codable {
var no: Int?
var category: Int?
var template_seq: Int?
}
And sample json data is .. below.
{
"data": {
"result" : 1
"total_count": 523,
"list": [
{
"no": 16398,
"category" : 23,
"template_seq" : 1
},
{
"no": -1,
"category" : 23,
"template_seq" : 1
}
]
}
}
But i want filtering wrong data.
If the value of "no" is less than or equal to 0, it is an invalid value.
Before not using codable...below.
(using Alamifre ison response )
guard let dictionaryData = responseJSON as? [String : Any] else { return nil }
guard let resultCode = dictionaryData["result"] as? Bool , resultCode == true else { return nil }
guard let theContainedData = dictionaryData["data"] as? [String:Any] else { return nil }
guard let sampleListData = theContainedData["list"] as? [[String : Any]] else { return nil }
var myListData = [MyEstimateListData]()
for theSample in sampleListData {
guard let existNo = theSample["no"] as? Int, existNo > 0 else {
continue
}
myListData.append( ... )
}
return myListData
how to filter wrong data or invalid data using swift 4.0 Codable ??
you can make codable for inital resonse
Here is your model:
import Foundation
struct Initial: Codable {
let data: DataModel?
}
struct DataModel: Codable {
let result, totalCount: Int
let list: [List]?
enum CodingKeys: String, CodingKey {
case result
case totalCount = "total_count"
case list
}
}
struct List: Codable {
let no, category, templateSeq: Int
enum CodingKeys: String, CodingKey {
case no, category
case templateSeq = "template_seq"
}
}
extension Initial {
init(data: Data) throws {
self = try JSONDecoder().decode(Initial.self, from: data)
}
}
And use it like that :
if let initail = try? Initial.init(data: data) , let list = initail.data?.list {
var myListData = list.filter { $0.no > 0 }
}
Yes, you have to use filters for that with codable:
1: Your struct should be according to your response like that:
struct SampleModel : Codable {
var result: Int?
var total_count: Int?
var list: [List]?
}
struct List : Codable {
var no: Int?
var category: Int?
var template_seq: Int?
}
2: Parse your response using a codable struct like that:
do {
let jsonData = try JSONSerialization.data(withJSONObject: dictionaryData["data"] as Any, options: JSONSerialization.WritingOptions.prettyPrinted)
let resultData = try JSONDecoder().decode(SampleModel.self, from: jsonData)
success(result as AnyObject)
} catch let message {
print("JSON serialization error:" + "\(message)")
}
3: now you can filter invalid data simply:
let filterListData = resultData.list?.filter({$0.no > 0})
let invalidData = resultData.list?.filter({$0.no <= 0})