Trying to find unique dataset from JSON using my parameter.
I've got JSON "userDetails":
{
"userDetails": [
{
"name": "Sabine",
"imageUrl" :"https://randomuser.me/api/portraits/men/82.jpg",
"content": [
{
"type": "video",
"url": "https://storage.googleapis.com/coverr-main/mp4/Travaho.mp4"
},
{
"type": "image",
"url": "https://picsum.photos/640/1136/?image=281"
}
]
},
{"name": "Keila Maney",
"imageUrl" :"https://randomuser.me/api/portraits/women/81.jpg",
"content": [
{
"type": "image",
"url": "https://picsum.photos/640/1136/?image=273"
},
{
"type": "video",
"url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4"
}
]
}
]
}
I know that my user is Keila Maney (I've got that users attribute in my code) and need to send that parameter to JSON, and receive back ONLY Keila Maney attributes (it's imageUrl, content (type, url)) and then parse them.
Are there any standard methods for searching in JSON?
Thanks in advance!
Just create object of UserDetails like: var userData: [UserDetails]?
and check this in a loop userData[x].name == "Keila Maney". then this x will be your index for Keila Maney and your can fetch its all data. After binding your json data to UserModel object. These are the three classes you will create from your json
UserModel
struct UserModel : Codable {
let userDetails : [UserDetails]?
enum CodingKeys: String, CodingKey {
case userDetails = "userDetails"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
userDetails = try values.decodeIfPresent([UserDetails].self, forKey:.userDetails)
}
}
UserDetails
struct UserDetails : Codable {
let name : String?
let imageUrl : String?
let content : [Content]?
enum CodingKeys: String, CodingKey {
case name = "name"
case imageUrl = "imageUrl"
case content = "content"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decodeIfPresent(String.self, forKey: .name)
imageUrl = try values.decodeIfPresent(String.self, forKey: .imageUrl)
content = try values.decodeIfPresent([Content].self, forKey: .content)
}
}
Content
struct Content : Codable {
let type : String?
let url : String?
enum CodingKeys: String, CodingKey {
case type = "type"
case url = "url"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decodeIfPresent(String.self, forKey: .type)
url = try values.decodeIfPresent(String.self, forKey: .url)
}
}
Example of Json:
{
"places": [
{
"name": "Ulrikh",
"id": 555,
},
{
"name": "Place 2",
"id": 1,
}
]
}
Call:
self.getDataByID(id: 555)
Swift Methods:
func getDataByID(id : Int) {
let data = self.getData()
for i in 0..<data.count
{
if(Int((((data as NSDictionary)["places"] as! NSArray)[i] as! NSDictionary)["id"] as! Int) == id)
{
print("here is result:")
print((((data as NSDictionary)["places"] as! NSArray)[i]))
}
}
}
func getData() -> NSDictionary {
do{
let path = Bundle.main.path(forResource: "test", ofType: "json")
let jsonData = NSData(contentsOfFile: path!)
let jsonResult:NSDictionary! = try JSONSerialization.jsonObject(with: jsonData! as Data , options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
return jsonResult
} catch let error as NSError {
print(error)
}
return NSDictionary()
}
don't thank :)
Related
I'm trying data parsing from server with Alamofire.
(I've been trying for 2 days and it's failing.)
How can I get Json array Codable type with Alamofire?...
API :
[
{
"name": "John Doe",
"email": "johndoe#gmail.com",
"type": "Lattee",
"size": "medium"
},
{
"name": "Doe",
"email": "doe#gmail.com",
"type": "Lattee",
"size": "small"
}
]
now this is my code
in Model.swift
struct OrderList : Codable{
var list : [Order]
}
enum coffeeType: String, Codable{
case cappuccino
case lattee
case espressino
case cortado
}
enum coffeeSize: String, Codable{
case small
case medium
case large
enum CodingKeys: String, CodingKey {
case small = "s"
case medium = "m"
case large = "l"
}
}
struct Order: Codable {
let email: String!
let name : String!
let size : coffeeSize!
let type : coffeeType!
enum CodingKeys: String, CodingKey{
case name = "Name"
case email = "Email"
case type = "Type"
case size = "Size"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
email = try values.decodeIfPresent(String.self, forKey: .email) ?? ""
name = try values.decodeIfPresent(String.self, forKey: .name) ?? "Guest"
size = try values.decodeIfPresent(coffeeSize.self, forKey: .size) ?? .small
type = try values.decodeIfPresent(coffeeType.self, forKey: .type) ?? .lattee
}
}
struct Resource<T: Codable> {
let url : URL
var httpMethod: HTTPMethod = .get
}
I have defined it in various formats such as responseData, responseJSON, and responseCodable, but I keep getting nil or something is missing.
I know how to parse with responseJSON. but I want trying to parse by applying Codable...
it's too difficult.
---- data parsing ---
func load<T>(resource: Resource<T>, completion: #escaping (Result<T, NetworkError>) -> Void) {
let call = AF.request(myurl,method: resource.httpMethod, parameters: nil).responseJSON{ response in
switch response.result {
case .success(let data):
if let JSON = response.value {
do{
let dataJson = try JSONSerialization.data(withJSONObject: JSON, options: [])
let getInstanceData = try JSONDecoder().decode(T.self, from: dataJson)
print(getInstanceData)
completion(.success(getInstanceData))
}catch{
print(error)
}
}
case .failure(_):
break
}
}
}
Since the API returns this payload:
[ { "name": "John Doe", "email": "johndoe#gmail.com", "type": "Lattee", "size": "medium" }, { "name": "Doe", "email": "doe#gmail.com", "type": "Lattee", "size": "small" } ]
these are keys that your code needs to handle:
name, email, type, size
Therefore the Order struct should be:
struct Order: Codable {
let email: String
let name : String
let size : CoffeeSize
let type : CoffeeType
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
email = try values.decodeIfPresent(String.self, forKey: .email) ?? ""
name = try values.decodeIfPresent(String.self, forKey: .name) ?? "Guest"
size = try values.decode(CoffeeSize.self, forKey: .size)
type = try values.decode(CoffeeType.self, forKey: .type)
}
}
enum CoffeeType: String, Codable {
case cappuccino
case latte
case espressino
case cortado
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(String.self)
let lowercaseLabel = label.lowercased()
self = CoffeeType(rawValue: lowercaseLabel) ?? .latte
}
}
enum CoffeeSize: String, Codable {
case small
case medium
case large
}
since there is no list key in the json structure therefore you don't need
struct OrderList. In order to get a list of orders you can simply call
load(resource<[Order]>) { result in
// handle the response but now you will get the list of orders
}
Alternatively, I created a playground to load a json file (data.json) locally and parse it into objects you can have a look at the solution below
func readJsonFile(filename: String) -> String {
guard let fileUrl = Bundle.main.url(forResource: filename, withExtension: "json") else { fatalError() }
guard let jsonData = try? String(contentsOf: fileUrl) else {
return ""
}
return jsonData
}
enum CoffeeType: String, Codable {
case cappuccino
case latte
case espressino
case cortado
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(String.self)
let lowercaseLabel = label.lowercased()
self = CoffeeType(rawValue: lowercaseLabel) ?? .latte
}
}
enum CoffeeSize: String, Codable {
case small
case medium
case large
}
struct Order: Codable {
let email: String
let name : String
let size : CoffeeSize
let type : CoffeeType
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
email = try values.decodeIfPresent(String.self, forKey: .email) ?? ""
name = try values.decodeIfPresent(String.self, forKey: .name) ?? "Guest"
size = try values.decode(CoffeeSize.self, forKey: .size)
type = try values.decode(CoffeeType.self, forKey: .type)
}
}
func parseJsonFile() {
let jsonStr = readJsonFile(filename: "data")
let jsonData: Data = Data(jsonStr.utf8)
let decoder = JSONDecoder()
do {
let orders = try decoder.decode([Order].self, from: jsonData)
orders.forEach {
print("\($0.name)" + " - " + "\($0.type.rawValue)" + " - " + "\($0.size.rawValue)")
}
} catch {
print(error.localizedDescription)
}
}
parseJsonFile()
Result:
John Doe - latte - medium
Doe - latte - small
The problem with your code is case-sensitive properties.
If the API returns these keys Name, Type, Email, Size, Type then the Codable object should have the coding Keys to handle keys from API
struct Order: Codable {
let email: String
let name : String
let size : CoffeeSize
let type : CoffeeType
enum CodingKeys: String, CodingKey{
case name = "Name"
case email = "Email"
case type = "Type"
case size = "Size"
}
}
In terms of unwrapping properties, it will depend on the contract between your code and API. If you're pretty sure API will always return size,type, name but email is nullable. Then you should use the method decodeIfPresent allows the value of the parsing key is nullable
If you're certain about name, the value is always not null, then you can use decode normally
and your Order struct will become
struct Order: Codable {
let email: String?
let name : String
let size : CoffeeSize
let type : CoffeeType
enum CodingKeys: String, CodingKey{
case name = "Name"
case email = "Email"
case type = "Type"
case size = "Size"
}
}
The JSON response is:-
{
"id" = "1"
"message" = "SUCCESS"
"data" = "[{\"name\":"FirstName",\"office_id\":1111,\"days_name\":\"Mon\"},
{\"name\":"SecondName:,\"office_id\":1112,\"days_name\":\"Tue\"}]"
}
I don't seems to understand how to approach decoding "data", In data model shall the data be declared as String? and I have been trying to figure out but don't seem to get any clue and stuck here for a while, if anyone can please shed some light to it, it would help a lot. The only problem I am facing is how to deal with "" double quotes wrapped around data array as shown in above response.
Data model and URLSession code is below:
struct Root : Codable {
let id : String?
let message : String?
let data : String?
enum CodingKeys: String, CodingKey {
case id = "id"
case message = "message"
case data = "data"
}
}
struct insideData: Codable {
let name: String?
let officeId : Int?
let daysName: String?
enum CodingKeys: String, CodingKey {
case name = "name"
case officeId = "office_id"
case daysName = "days_name"
}
}
URLSession.shared.dataTask(with: url!) { (responseData, httpUrlResponse , error) in
if(error == nil && responseData != nil && responseData?.count != 0){
let decoder = JSONDecoder()
do{
let result = try decoder.decode(Root.self, from: responseData!)
print(result.data!)
}
catch let error {
debugPrint("Error occured while decoding = \(error.localizedDescription)")
}
}
}.resume()
I save result.data! in a new variable and convert it to data and again use JSONDecoder but now with insideData.self struct but don't get desired output, not getting mapped with keys inside insideData struct.
I am just getting started with learning networking in swift so please pardon me for silly mistakes.
data value is JSON with JSON, ie it's a JSONString.
A way to parse it is to parse again the JSON String. To do so, you need to override init(from decoder:) of Root.
Let's first fix your JSON which isn't valid, to be able to use it in Playgrounds.
let jsonString = #"""
{
"id": "1",
"message": "SUCCESS",
"data": "[{\"name\":\"FirstName\",\"office_id\":1111,\"days_name\":\"Mon\"}, {\"name\":\"SecondName:\", \"office_id\":1112,\"days_name\":\"Tue\"}]"
}
"""#
Then, change data: let data : [InsideData]
You could then:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: .id)
self.message = try container.decodeIfPresent(String.self, forKey: .message)
guard let dataString = try container.decodeIfPresent(String.self, forKey: .data) else {
self.data = []
return
}
self.data = try JSONDecoder().decode([InsideData].self, from: Data(dataString.utf8))
}
If you don't like creating a new decoder, you can pass one in userInfo of the decoder:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: .id)
self.message = try container.decodeIfPresent(String.self, forKey: .message)
guard let dataString = try container.decodeIfPresent(String.self, forKey: .data) else {
self.data = []
return
}
guard let insideDecoder = decoder.userInfo[CodingUserInfoKey(rawValue: "InsideDecoder")!] as? JSONDecoder else {
self.data = []
return
}
self.data = try insideDecoder.decode([InsideData].self, from: Data(dataString.utf8))
}
Then the root decoding:
let decoder = JSONDecoder()
let insideDecoder = JSONDecoder()
decoder.userInfo = [CodingUserInfoKey(rawValue: "InsideDecoder")!: insideDecoder]
do {
let root = try decoder.decode(Root.self, from: Data(jsonString.utf8))
print(root)
} catch {
print("Error: \(error)")
}
The provided JSON looks to be invalid.
In json you don't use the = sign but the : sign between a key and value.
You are missing , behind each value.
There is a typo behind SecondName :, should be ",
It's weird to have quotes around your data array. It can be easier decoded when you have them removed.
Suggested JSON changes:
{
"id": "1",
"message": "SUCCESS",
"data": [
{"name":"FirstName","office_id":1111,"days_name":"Mon"},
{"name":"SecondName","office_id":1112,"days_name":"Tue"}
]
}
I've tested decoding this json in Playground and it seems to work:
struct DataEntity: Decodable {
let name: String
let officeId: Int
let daysName: String
enum CodingKeys: String, CodingKey {
case name = "name"
case officeId = "office_id"
case daysName = "days_name"
}
}
struct RootEntity: Decodable {
let id: String
let message: String
let data: [DataEntity]
}
struct Mapper {
func map(json: String) -> RootEntity {
guard let rootJsonData = json.data(using: .utf8) else {
fatalError("Couldn't convert json string to data")
}
do {
let rootEntity = try JSONDecoder().decode(RootEntity.self, from: rootJsonData)
return rootEntity
} catch let error {
fatalError(error.localizedDescription)
}
}
}
let jsonToDecode = """
{
"id": "1",
"message": "SUCCESS",
"data": [
{"name":"FirstName","office_id":1111,"days_name":"Mon"},
{"name":"SecondName","office_id":1112,"days_name":"Tue"}
]
}
"""
let rootEntity = Mapper().map(json: jsonToDecode)
print(rootEntity)
Print output:
RootEntity(id: "1", message: "SUCCESS", data: [DataEntity(name: "FirstName", officeId: 1111, daysName: "Mon"), DataEntity(name: "SecondName", officeId: 1112, daysName: "Tue")])
Given the following JSON from a network request; If you wanted to decode this into a Swift object that coforms to Codable, but you wanted to retain the nested JSON that is the value for the key configuration_payload, how could you do it?
{
"registration": {
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
}
Using the following Swift struct, I want to be able to grab the configuration_payload as a String.
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let error: String?
public let thingUUID: Int?
public let discoveryTimeout, installationTimeout: Int
public let configurationPayload: String?
}
As far as I can tell, the JSONDecoder in Swift, sees the value for configuration_payload as nested JSON and wants to decode it into it's own object. To add to confusion, configuration_payload is not always going to return the same JSON structure, it will vary, so I can not create a Swift struct that I can expect and simply JSON encode it again when needed. I need to be able to store the value as a String to account for variations in the JSON under the configuration_payload key.
As others have already said, you cannot just keep a part without decoding. However, decoding unknown data is trivial:
enum RawJsonValue {
case boolean(Bool)
case number(Double)
case string(String)
case array([RawJsonValue?])
case object([String: RawJsonValue])
}
extension RawJsonValue: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let boolValue = try? container.decode(Bool.self) {
self = .boolean(boolValue)
} else if let numberValue = try? container.decode(Double.self) {
self = .number(numberValue)
} else if let stringValue = try? container.decode(String.self) {
self = .string(stringValue)
} else if let arrayValue = try? container.decode([RawJsonValue?].self) {
self = .array(arrayValue)
} else {
let objectValue = try container.decode([String: RawJsonValue].self)
self = .object(objectValue)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .boolean(let boolValue):
try container.encode(boolValue)
case .number(let numberValue):
try container.encode(numberValue)
case .string(let stringValue):
try container.encode(stringValue)
case .array(let arrayValue):
try container.encode(arrayValue)
case .object(let objectValue):
try container.encode(objectValue)
}
}
}
Now we can safely decode and convert to JSON string if needed:
struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
let id, deviceType: String
let state: State
let error: String?
let thingUUID: Int?
let discoveryTimeout, installationTimeout: Int
let configurationPayload: RawJsonValue?
}
let jsonData = """
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let registration = try! decoder.decode(Registration.self, from: jsonData)
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let payloadString = String(data: try! encoder.encode(registration.configurationPayload), encoding: .utf8)!
print(payloadString) // {"title":"Some Title","views":9999,"url":"https:\/\/www.someurl.com\/","category":"test"}
The only problem I can see is potential loss of precision when decoding decimal numbers, which is a known problem with Foundation JSON decoder.
Also, some null values could be also removed. This could be fixed by decoding object manually by iterating keys and having a special null type.
You can achieve decoding of a JSON object to [String: Any] by using a third party library like AnyCodable.
Your Registration struct will look like this:
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let error: String?
public let thingUUID: Int?
public let discoveryTimeout, installationTimeout: Int
public let configurationPayload: [String: AnyCodable]?
}
and then you can convert [String: AnyCodable] type to [String: Any] or even to String:
let jsonString = """
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
"""
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let registration = try decoder.decode(Registration.self, from: Data(jsonString.utf8))
// to [String: Any]
let dictionary = registration.configurationPayload?.mapValues { $0.value }
// to String
if let configurationPayload = registration.configurationPayload {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let data = try encoder.encode(configurationPayload)
let string = String(decoding: data, as: UTF8.self)
print(string)
}
} catch {
print(error)
}
One (more limited than you probably want) way would be to make sure that Value part in configuration_payload JSON is a known Codable single type (String) instead of Any which can produce multiple types (String, Int, Double etc.).
I was trying to make it work with [String: Any] for the configuration_payload, the problem is Any does NOT conform to Codable.
Then I tried with [String: String] for configuration_payload and was able to make it work like following.
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let thingUUID: Int?
public let discoveryTimeout, installationTimeout: Int
public let configurationPayload: [String: String]? // NOT [String: Any]?
enum CodingKeys: String, CodingKey {
case id = "id"
case deviceType = "device_type"
case state = "state"
case thingUUID = "thing_uuid"
case discoveryTimeout = "discovery_timeout"
case installationTimeout = "installation_timeout"
case configurationPayload = "configuration_payload"
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(String.self, forKey: .id) ?? ""
deviceType = try values.decodeIfPresent(String.self, forKey: .deviceType) ?? ""
let stateRaw = try values.decodeIfPresent(String.self, forKey: .state) ?? ""
state = Registration.State(rawValue: stateRaw) ?? .provisioning
thingUUID = try values.decodeIfPresent(Int.self, forKey: .thingUUID)
discoveryTimeout = try values.decodeIfPresent(Int.self, forKey: .discoveryTimeout) ?? 0
installationTimeout = try values.decodeIfPresent(Int.self, forKey: .installationTimeout) ?? 0
configurationPayload = try values.decodeIfPresent([String: String].self, forKey: .configurationPayload)
}
}
Tests
let json = Data("""
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload": {
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": "9999"
}
}
""".utf8
)
let decoded = try JSONDecoder().decode(Registration.self, from: json)
print(decoded)
let encoded = try JSONEncoder().encode(decoded)
print(String(data: encoded, encoding: .utf8))
This is not possible with the Codable protocol, because you do not know the type before hand. You'll have to either write your own method or have a different decoding strategy.
let json = """
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload": {
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
""".data(using: .utf8)
do {
let decoded = try? Registration.init(jsonData: json!)
print(decoded)
}catch {
print(error)
}
public struct Registration {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id: String
public let device_type: String
public let state: State
public let error: String?
public let thing_uuid: Int?
public let discovery_timeout, installation_timeout: Int
public let configuration_payload: [String: Any]?
public init(jsonData: Data) throws {
let package = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String : Any]
id = package["id"] as! String
device_type = package["device_type"] as! String
state = State(rawValue: package["state"] as! String)!
error = package["error"] as? String
thing_uuid = package["thing_uuid"] as? Int
discovery_timeout = package["discovery_timeout"] as! Int
installation_timeout = package["installation_timeout"] as! Int
configuration_payload = package["configuration_payload"] as? [String: Any]
}
}
This is one possible way to handle the different types. You could also create a struct containing keys and loop through them, I think this illustrates the basic idea though.
Edit:
if let remaining = package["configuration_payload"] as? Data,
let data = try? JSONSerialization.data(withJSONObject: remaining, options: []) as Data,
let string = String(data: data, encoding: .utf8) {
// store your string if you want it in string formatt
print(string)
}
If you have a list of possible keys, using optionals is another way you could employ Codable. You can mix keys this way - only the ones that are available will attempt to be encoded/decoded
import UIKit
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let error: String?
public let thingUuid: Int?
public let discoveryTimeout, installationTimeout: Int
public var configurationPayload: ConfigurationPayload?
}
// nested json can be represented as a codable struct
public struct ConfigurationPayload: Codable {
let title: String?
let url: String?
let category: String?
let views: Int?
let nonTitle: String?
let anotherUrl: String?
let someCategory: String?
let someViews: Int?
// computed properties aren't part of the coding strategy
// TODO: avoid duplication in loop
var jsonString: String {
let mirror = Mirror(reflecting: self).children
let parameters = mirror.compactMap({$0.label})
let values = mirror.map({$0.value})
let keyValueDict = zip(parameters, values)
var returnString: String = "{\n"
for (key, value) in keyValueDict {
if let value = value as? Int {
returnString.append("\"\(key)\": \"\(value)\n")
} else if let value = value as? String {
returnString.append("\"\(key)\": \"\(value)\n")
}
}
returnString.append("}")
return returnString
}
}
// your json has a preceding key of "registration", this is the type you will decode
public struct RegistrationParent: Codable {
var registration: Registration
}
let jsonDataA =
"""
{
"registration": {
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
}
""".data(using: .utf8)!
let jsonDataB =
"""
{
"registration": {
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"non_title": "Some Title",
"another_url": "https://www.someurl.com/",
"some_category": "test",
"some_views": 9999
}
}
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
var registrationA = try decoder.decode(RegistrationParent.self, from: jsonDataA)
print(registrationA.registration.configurationPayload?.jsonString ?? "{}")
var registrationB = try decoder.decode(RegistrationParent.self, from: jsonDataB)
print(registrationB.registration.configurationPayload?.jsonString ?? "{}")
} catch {
print(error)
}
here is configurationPayload is dictionary so your Registration struct look like below
struct Registration : Codable {
let configurationPayload : ConfigurationPayload?
let deviceType : String?
let discoveryTimeout : Int?
let id : String?
let installationTimeout : Int?
let state : String?
let thingUuid : Int?
enum CodingKeys: String, CodingKey {
case configurationPayload = "configuration_payload"
case deviceType = "device_type"
case discoveryTimeout = "discovery_timeout"
case id = "id"
case installationTimeout = "installation_timeout"
case state = "state"
case thingUuid = "thing_uuid"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
configurationPayload = ConfigurationPayload(from: decoder)
deviceType = try values.decodeIfPresent(String.self, forKey: .deviceType)
discoveryTimeout = try values.decodeIfPresent(Int.self, forKey: .discoveryTimeout)
id = try values.decodeIfPresent(String.self, forKey: .id)
installationTimeout = try values.decodeIfPresent(Int.self, forKey: .installationTimeout)
state = try values.decodeIfPresent(String.self, forKey: .state)
thingUuid = try values.decodeIfPresent(Int.self, forKey: .thingUuid)
}
}
and your ConfigurationPayload look like this
struct ConfigurationPayload : Codable {
let category : String?
let title : String?
let url : String?
let views : Int?
enum CodingKeys: String, CodingKey {
case category = "category"
case title = "title"
case url = "url"
case views = "views"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
category = try values.decodeIfPresent(String.self, forKey: .category)
title = try values.decodeIfPresent(String.self, forKey: .title)
url = try values.decodeIfPresent(String.self, forKey: .url)
views = try values.decodeIfPresent(Int.self, forKey: .views)
}
}
I'm trying to parse a test JSON from a http adress, but I get an error saying that
"No value associated with key CodingKeys(stringValue: \"name\", intValue: nil)
The JSON looks like this. It has been validated, so it should work ok:
{
"work": [
{
"name": "Jocke",
"job": "Developer",
"date": "1985-12-30T00:00:00+0000",
"best_book": {
"id": 33245,
"title": "DiscWorld",
"author": {
"id": 345,
"name": "Terry Prattchet"
}
}
},
{
"name": "Bob",
"job": "Construction worker",
"date": "2010-01-30T00:00:00+0000",
"best_book": {
"id": 375802,
"title": "Ender's Game (Ender's Saga, #1)",
"author": {
"id": 589,
"name": "Orson Scott Card"
}
}
}
]
}
The code looks like this:
struct People: Codable {
let name: String
let job: String
enum OuterKey: String, CodingKey {
case work = "work"
}
enum codingKeys: String, CodingKey {
case name = "name"
case job = "job"
}
init(decoder: Decoder) throws {
let outerContainer = try decoder.container(keyedBy: OuterKey.self)
let innerContainer = try outerContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .work)
self.name = try innerContainer.decode(String.self, forKey: .name)
self.job = try innerContainer.decode(String.self, forKey: .job)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "https://api.myjson.com/bins/fe2eo") else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
let decodedJson = try! jsonDecoder.decode([People].self, from: data)
}
}.resume()
}
}
I'm just trying to grasp the first two keys as of now, just to see if it works. But it doesn't even get past name.
Your api returns
[{"firstName":"Jocke","job":"developer"},{"firstName":"Anna","job":"construction"},{"firstName":"Peter","job":"pilot"}]
do {
let jsonDecoder = JSONDecoder()
let decodedJson = try jsonDecoder.decode([People].self, from: data)
}
catch {
print(error)
}
struct People: Codable {
let firstName, job: String
}
just try this
struct Work:Codeable {
let work:[People]
}
struct People: Codable {
let name: String
let job: String
}
do {
let jsonDecoder = JSONDecoder()
let decodedJson = try jsonDecoder.decode(Work.self, from: data)
}
catch {
print(error)
}
if you have same name as json keys you don't need to use codingkeys
I am using following decoding struct to decode the data from server but it always returns "No value associated with key CodingKeys". Please see the code below
struct UserDecode: Codable {
var user: User?
var result: String?
private enum CodingKeys: String, CodingKey {
case user = "Records"
case result = "Result"
}
init(from decoder: Decoder) throws {
do {
let values = try decoder.container(keyedBy: CodingKeys.self)
result = try values.decode(String.self, forKey: .result)
user = try values.decode(User.self, forKey: .user)
} catch {
print(error.localizedDescription)
}
}
}
struct User: Codable {
var mobile: Int?
var userId: Int?
var name: String?
private enum CodingKeys: String, CodingKey {
case userId = "MEMBERID"
case name = "Membername"
case mobile = "Mobile"
}
init(from decoder: Decoder) throws {
do {
let values = try decoder.container(keyedBy: CodingKeys.self)
userId = try values.decode(Int.self, forKey: .userId)
name = try values.decode(String.self, forKey: .name)
mobile = try values.decode(Int.self, forKey: .mobile)
}
catch {
print(error.localizedDescription)
}
}
}
and the response from server is
{
"Result": "OK",
"Records": {
"MEMBERID": 1,
"Membername": "Balaji",
"Mobile": 12345678901,
}
}
The error i am getting is DecodingError
▿ keyNotFound : 2 elements
- .0 : UserCodingKeys(stringValue: "Result", intValue: nil)
▿ .1 : Context
- codingPath : 0 elements
- debugDescription : "No value associated with key UserCodingKeys(stringValue: \"Result\", intValue: nil) (\"Result\")."
- underlyingError : nil
This is the full data from server.
{
"Result": "OK",
"Records": {
"MEMBERID": 1,
"Membername": "Balaji",
"Mobile": 12345678901,
"PAYLEVEL": 0,
"UserName": "12345678901",
"Token": "SroRn65YfqLSGcMMHaFsl2c5RGEiv1JcHHPnpFXa7quKOPIRsqUEhpcWpGKl_23O4PJcgmLiFb9T8TAq1fgyftgpffJKCbJUozYjKF68dvChrb8Qv1egw_paxnUZlYBzwlfXUtFQ23Y1Wu6UBZHdycY4PwS9a1f_e1zG_etiV9R-E8kOLMoqwQTXTtRZ3NPEXsHDtS3KRz471c9Bzbx_v3FGw4HDcvGgejWaC1Zo6nHE8IQ7MG2oX5KuneZTqd1X",
"Imageurl": "https://.net/images/abc.png",
"WalletBalance": 0,
"ResponseCode": 1,
"ResponseMessage": "LOGIN SUCCESS",
"ImageType": "1.jpeg",
"Emailid": "ab.net",
"FirstOrder": 1,
"FirstOrderImage": "https:.net/images/abc.png"
}
}
Decoder code:
let task = session.dataTask(with: request) {(data, response, error) in
let (success, errorMessage) = self.isValidResponse(error: error, data: data, response: response as? HTTPURLResponse)
if !success {
completionHandler(nil, errorMessage)
return
}
// Parse the data
do {
let decoder = JSONDecoder()
if let userResult = try decoder.decode(UserDecode.self, from: data!) as? UserDecode {
completionHandler(userResult.user, nil)
return
}
else {
completionHandler(nil, ErrorMessage.unableToProcess)
return
}
} catch {
print("Could not parse JSON data")
completionHandler(nil, ErrorMessage.unableToProcess)
return
}
}
Having
let values = try decoder.container(keyedBy: CodingKeys.self)
result = try values.decode(String.self, forKey: .result)
decode makes the decoding fails if data is nil consider using decodeIfPresent or the optional ? suffices