I have a model called College
class College : Decodable {
let name : String
let id : String
let iconUrl : String
}
And a few college related APIs, each with a slightly different response. 2 examples are
GET api/v1/colleges
response JSON for this API is
{
"success": String,
"colleges": [College]
}
GET api/v1/college/{collegeID}
response JSON for this API is
{
"success": String,
"college": College
}
Now, from both the responses I need to get only the college information, the "success" key is not useful to me. My question is, how to get the college information without creating separate response models for each API? Currently I have implemented separate classes for each API response
class GetCollegesResponse : Decodable {
let success : String
let colleges : [College]
}
class GetCollegeResponse : Decodable {
let success : String
let college : College
}
And I use them in respective API calls like so
Alamofire.request(api/v1/colleges ....).responseJSON { response in
let resp = JSONDecoder().decode(GetCollegesResponse.self, response.data)
//get colleges from resp.colleges
}
Alamofire.request(api/v1/college/\(id) ....).responseJSON { response in
let resp = JSONDecoder().decode(GetCollegeResponse.self, response.data)
// get college form resp.college
}
Is there a simpler way to get this done?
Probably the right approach is to model the response as a generic type, like something like this:
struct APIResponse<T: Decodable> {
let success: String
let payload: T
}
from which you could extract the payload.
The problem is that the key with the payload changes: it's college for a single result and colleges for multiple college results.
If you truly don't care and just want the payload, we could effectively ignore it and decode any key (other than "success") as an expected type T:
struct APIResponse<T: Decodable> {
let success: String
let payload: T
// represents any string key
struct ResponseKey: CodingKey {
var stringValue: String
var intValue: Int? = nil
init(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) { return nil }
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ResponseKey.self)
let sKey = container.allKeys.first(where: { $0.stringValue == "success" })
let pKey = container.allKeys.first(where: { $0.stringValue != "success" })
guard let success = sKey, let payload = pKey else {
throw DecodingError.keyNotFound(
ResponseKey(stringValue: "success|any"),
DecodingError.Context(
codingPath: container.codingPath,
debugDescription: "Expected success and any other key"))
}
self.success = try container.decode(String.self, forKey: success)
self.payload = try container.decode(T.self, forKey: payload)
}
}
Then you could decode based on the expected payload:
let resp = try JSONDecoder().decode(APIResponse<[College]>.self, response.data)
let colleges = resp.payload
I'm afraid there is no way to get the value of a particular item from within a json response without creating a model for the whole response. (at least using codable)
First of all the payload sent from the server will be received by us in an encoded form (UTF8). So we have to decode it first before using it and here is where codable comes in to help us decode our data. If you wish to see the raw conversion using string try this method.
let dataConvertedToString = String(data: dataReceivedFromServer, encoding: .utf8)
If you still prefer getting just the value alone from the JSON response I would suggest you to use SwiftyJSON. It is a cocoapod framework. You can use SwiftyJSON like this.
let json = try! JSON(data: dataFromServer)
json["success"].boolValue
json["college"]["name"].stringValue
Related
I am very new to Swift, so please pardon me if it is a silly question
How to handle this type of response into data model ,response is in this form:
{
"id" = 1;
count = "";
data = "[{"key":"value","key":"value".......}]";
message = SUCCESS;
"response_code" = 1;
}
The AlamofireRequest gets the response and prints it but when using responseDecodable,nothing happens, the code of Alamofire request is below:
let request = AF.request(urlString!,method: .get)
request.responseJSON { (data) in
print(data)
}
request.responseDecodable(of: Test.self) { (response) in
guard let getData = response.value else {return}
print(getData.all[0].firstName) //Nothing prints
}
And this is how the data model looks like:
struct Test: Decodable {
let firstName: String
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
//firstname is inside data of json
}
}
struct Getdata: Decodable {
let all : [Test]
enum CodingKeys: String, CodingKey {
case all = "data"
}
}
Want to access values inside data and print it. Please shed some light on it!
Trying to address both the question and the comments above, the first thing would be to get some valid JSON. It could be that your API is providing non-JSON data, in which case neither this answer or using AlamoFire to decode it will work. However, formatting the above as JSON, at my best guess of what it's meant to be, will give:
let json = """
{
"id": 1,
"count": "",
"data": [
{
"key1": "value1",
"key2": "value2"
}
],
"message": "SUCCESS",
"response_code": 1
}
"""
At this point it's more obvious that the content under the data key is not an array of Test as defined above, but is an array of dictionaries (with a single enry in the array in the example). So it will be necessary to respecify the data model. For something this simple, for the use case mentioned, there is no real reason to go for multiple types, so we're going to redefine Getdata (I'm sticking with your naming despite not liking it):
struct Getdata: Decodable {
let all : [[String:String]]
enum CodingKeys: String, CodingKey {
case all = "data"
}
}
To test the decoding let's used the standard JSONDecoder (I don't have a Playground handy with AlamoFire as I'd never use it):
do {
let wrapper = try JSONDecoder().decode(Getdata.self, from: Data(json.utf8))
print(wrapper.all)
} catch {
print("Decoding error \(error.localizedDescription)")
}
This outputs
[["key1": "value1", "key2": "value2"]]
as would be expected.
Now there's a valid JSON decoding solution it should be easy to drop this into the AlamoFire APIs if you so wish. Although if the backend really is providing the misformed JSON as in question this won't work and you'll have to change the backend, decode it with your own decoder or mangle it into valid JSON.
Did lot of silly mistakes in original question, long way to go
As suggested by #flanker I did respecify the data model to
struct Test : Codable {
let id : String?
let message : String?
let data : String?
let count : String?
let response_code : String?
enum CodingKeys: String, CodingKey {
case id = "$id"
case message = "message"
case data = "data"
case count = "count"
case response_code = "response_code"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(String.self, forKey: .id)
message = try values.decodeIfPresent(String.self, forKey: .message)
data = try values.decodeIfPresent(String.self, forKey: .data)
count = try values.decodeIfPresent(String.self, forKey: .count)
response_code = try values.decodeIfPresent(String.self, forKey: .response_code)
}
And now it seems to work
Now getting the desired output
[["key1": "value1", "key2": "value2"]]
How to parse more then one JSON which each ending with null character(through socket TCP/IP).
{"ObjectID":"UHJvY1dpcmVsZXNzTXNn","DeviceCode":"RUNEOjI=","ActiveInputNames":"Q2hlY2sgaW4gRmFpbA==","DeviceInputNo":"999999","Activation":false,"Reset":true,"LocationID":"","LocationGroupText":"","ProtocolText":"","CallBackNo":"OTE5MTgyNTcyMjQ5"}��{"ObjectID":"VFBpbmdPYmplY3Q="}��
As you can see the above response which has 2 JSON's each ending with null character...I can easily parse the single JSON but unable to parse more then one JSON..
It would be great if any one suggest any solutions!!
First of all separate both the JSONs using components(separatedBy:) so we can parse them individually.
let str = """
{"ObjectID":"UHJvY1dpcmVsZXNzTXNn","DeviceCode":"RUNEOjI=","ActiveInputNames":"Q2hlY2sgaW4gRmFpbA==","DeviceInputNo":"999999","Activation":false,"Reset":true,"LocationID":"","LocationGroupText":"","ProtocolText":"","CallBackNo":"OTE5MTgyNTcyMjQ5"}��{"ObjectID":"VFBpbmdPYmplY3Q="}��
"""
let jsonArr = str.components(separatedBy: "��")
jsonArr contains both the JSON Strings. Let's see how we can parse them.
We'll use Codable to parse both the JSONs using the below model.
struct Root: Codable {
let objectID: String
let deviceCode: String?
let activeInputNames: String?
let deviceInputNo: String?
let activation: Bool?
let reset: Bool?
let locationID: String?
let locationGroupText: String?
let protocolText: String?
let callBackNo: String?
enum CodingKeys: String, CodingKey {
case objectID = "ObjectID"
case deviceCode = "DeviceCode"
case activeInputNames = "ActiveInputNames"
case deviceInputNo = "DeviceInputNo"
case activation = "Activation"
case reset = "Reset"
case locationID = "LocationID"
case locationGroupText = "LocationGroupText"
case protocolText = "ProtocolText"
case callBackNo = "CallBackNo"
}
}
Parse the JSONstrings like,
let parsedObjs = jsonArr.map { (str) -> Root? in
if let data = str.data(using: .utf8) {
do {
let obj = try JSONDecoder().decode(Root.self, from: data)
return obj
} catch {
print(error)
return nil
}
}
return nil
}
parsedObjs will contain parsed Root objects for both the JSON strings.
Let me know if there is any confusion left regarding this.
I answered the same question in Android Yesterday. Here is the Swift Version
let s = "{\"ObjectID\":\"UHJvY1dpcmVsZXNzTXNn\",\"DeviceCode\":\"RUNEOjI=\",\"ActiveInputNames\":\"Q2hlY2sgaW4gRmFpbA==\",\"DeviceInputNo\":\"999999\",\"Activation\":false,\"Reset\":true,\"LocationID\":\"\",\"LocationGroupText\":\"\",\"ProtocolText\":\"\",\"CallBackNo\":\"OTE5MTgyNTcyMjQ5\"}��{\"ObjectID\":\"VFBpbmdPYmplY3Q=\"}��".components(separatedBy: "��")
for string in s{
// do your parsing here
print(string)
}
All you need to do is split the string with �� and you are good to go. Parse the JSON the way you used to.
I have the following code to extract a JSON contained within a coding key:
let value = try! decoder.decode([String:Applmusic].self, from: $0["applmusic"])
This successfully handles the following JSONs:
{
"applmusic":{
"code":"AAPL",
"quality":"good",
"line":"She told me don't worry",
}
However, fails to extract a JSON with the coding key of applmusic from the following one:
{
"applmusic":{
"code":"AAPL",
"quality":"good",
"line":"She told me don't worry",
},
"spotify":{
"differentcode":"SPOT",
"music_quality":"good",
"spotify_specific_code":"absent in apple"
},
"amazon":{
"amzncode":"SPOT",
"music_quality":"good",
"stanley":"absent in apple"
}
}
The data models for applmusic,spotify and amazon are different. However, I need only to extract applmusic and omit other coding keys.
My Swift data model is the following:
public struct Applmusic: Codable {
public let code: String
public let quality: String
public let line: String
}
The API responds with the full JSON and I cannot ask it to give me only the needed fields.
How to decode only the specific part of the json? It seems, that Decodable requires me to deserialize the whole json first, so I have to know the full data model for it.
Obviously, one of the solutions would be to create a separate Response model just to contain the applmusicparameter, but it looks like a hack:
public struct Response: Codable {
public struct Applmusic: Codable {
public let code: String
public let quality: String
public let line: String
}
// The only parameter is `applmusic`, ignoring the other parts - works fine
public let applmusic: Applmusic
}
Could you propose a better way to deal with such JSON structures?
A little bit more insight
I use it the following technique in the generic extension that automatically decodes the API responses for me. Therefore, I'd prefer to generalize a way for handling such cases, without the need to create a Root structure. What if the key I need is 3 layers deep in the JSON structure?
Here is the extension that does the decoding for me:
extension Endpoint where Response: Swift.Decodable {
convenience init(method: Method = .get,
path: Path,
codingKey: String? = nil,
parameters: Parameters? = nil) {
self.init(method: method, path: path, parameters: parameters, codingKey: codingKey) {
if let key = codingKey {
guard let value = try decoder.decode([String:Response].self, from: $0)[key] else {
throw RestClientError.valueNotFound(codingKey: key)
}
return value
}
return try decoder.decode(Response.self, from: $0)
}
}
}
The API is defined like this:
extension API {
static func getMusic() -> Endpoint<[Applmusic]> {
return Endpoint(method: .get,
path: "/api/music",
codingKey: "applmusic")
}
}
Updated: I made an extension of JSONDecoder out of this answer, you can check it here: https://github.com/aunnnn/NestedDecodable, it allows you to decode a nested model of any depth with a key path.
You can use it like this:
let post = try decoder.decode(Post.self, from: data, keyPath: "nested.post")
You can make a Decodable wrapper (e.g., ModelResponse here), and put all the logic to extract nested model with a key inside that:
struct DecodingHelper {
/// Dynamic key
private struct Key: CodingKey {
let stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
let intValue: Int?
init?(intValue: Int) {
return nil
}
}
/// Dummy model that handles model extracting logic from a key
private struct ModelResponse<NestedModel: Decodable>: Decodable {
let nested: NestedModel
public init(from decoder: Decoder) throws {
let key = Key(stringValue: decoder.userInfo[CodingUserInfoKey(rawValue: "my_model_key")!]! as! String)!
let values = try decoder.container(keyedBy: Key.self)
nested = try values.decode(NestedModel.self, forKey: key)
}
}
static func decode<T: Decodable>(modelType: T.Type, fromKey key: String) throws -> T {
// mock data, replace with network response
let path = Bundle.main.path(forResource: "test", ofType: "json")!
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let decoder = JSONDecoder()
// ***Pass in our key through `userInfo`
decoder.userInfo[CodingUserInfoKey(rawValue: "my_model_key")!] = key
let model = try decoder.decode(ModelResponse<T>.self, from: data).nested
return model
}
}
You can pass your desired key through userInfo of JSONDecoder ("my_model_key"). It is then converted to our dynamic Key inside ModelResponse to actually extract the model.
Then you can use it like this:
let appl = try DecodingHelper.decode(modelType: Applmusic.self, fromKey: "applmusic")
let amazon = try DecodingHelper.decode(modelType: Amazon.self, fromKey: "amazon")
let spotify = try DecodingHelper.decode(modelType: Spotify.self, fromKey: "spotify")
print(appl, amazon, spotify)
Full code:
https://gist.github.com/aunnnn/2d6bb20b9dfab41189a2411247d04904
Bonus: Deeply nested key
After playing around more, I found you can easily decode a key of arbitrary depth with this modified ModelResponse:
private struct ModelResponse<NestedModel: Decodable>: Decodable {
let nested: NestedModel
public init(from decoder: Decoder) throws {
// Split nested paths with '.'
var keyPaths = (decoder.userInfo[CodingUserInfoKey(rawValue: "my_model_key")!]! as! String).split(separator: ".")
// Get last key to extract in the end
let lastKey = String(keyPaths.popLast()!)
// Loop getting container until reach final one
var targetContainer = try decoder.container(keyedBy: Key.self)
for k in keyPaths {
let key = Key(stringValue: String(k))!
targetContainer = try targetContainer.nestedContainer(keyedBy: Key.self, forKey: key)
}
nested = try targetContainer.decode(NestedModel.self, forKey: Key(stringValue: lastKey)!)
}
Then you can use it like this:
let deeplyNestedModel = try DecodingHelper.decode(modelType: Amazon.self, fromKey: "nest1.nest2.nest3")
From this json:
{
"apple": { ... },
"amazon": {
"amzncode": "SPOT",
"music_quality": "good",
"stanley": "absent in apple"
},
"nest1": {
"nest2": {
"amzncode": "Nest works",
"music_quality": "Great",
"stanley": "Oh yes",
"nest3": {
"amzncode": "Nest works, again!!!",
"music_quality": "Great",
"stanley": "Oh yes"
}
}
}
}
Full code: https://gist.github.com/aunnnn/9a6b4608ae49fe1594dbcabd9e607834
You don't really need the nested struct Applmusic inside Response. This will do the job:
import Foundation
let json = """
{
"applmusic":{
"code":"AAPL",
"quality":"good",
"line":"She told me don't worry"
},
"I don't want this":"potatoe",
}
"""
public struct Applmusic: Codable {
public let code: String
public let quality: String
public let line: String
}
public struct Response: Codable {
public let applmusic: Applmusic
}
if let data = json.data(using: .utf8) {
let value = try! JSONDecoder().decode(Response.self, from: data).applmusic
print(value) // Applmusic(code: "AAPL", quality: "good", line: "She told me don\'t worry")
}
Edit: Addressing your latest comment
If the JSON response would change in a way that the applmusic tag is nested, you would only need to properly change your Response type. Example:
New JSON (note that applmusic is now nested in a new responseData tag):
{
"responseData":{
"applmusic":{
"code":"AAPL",
"quality":"good",
"line":"She told me don't worry"
},
"I don't want this":"potatoe",
}
}
The only change needed would be in Response:
public struct Response: Decodable {
public let applmusic: Applmusic
enum CodingKeys: String, CodingKey {
case responseData
}
enum ApplmusicKey: String, CodingKey {
case applmusic
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let applmusicKey = try values.nestedContainer(keyedBy: ApplmusicKey.self, forKey: .responseData)
applmusic = try applmusicKey.decode(Applmusic.self, forKey: .applmusic)
}
}
The previous changes wouldn't break up any existing code, we are only fine-tuning the private implementation of how the Response parses the JSON data to correctly fetch an Applmusic object. All calls such as JSONDecoder().decode(Response.self, from: data).applmusic would remain the same.
Tip
Finally, if you want to hide the Response wrapper logic altogether, you may have one public/exposed method which will do all the work; such as:
// (fine-tune this method to your needs)
func decodeAppleMusic(data: Data) throws -> Applmusic {
return try JSONDecoder().decode(Response.self, from: data).applmusic
}
Hiding the fact that Response even exists (make it private/inaccessible), will allow you to have all the code through your app only have to call decodeAppleMusic(data:). For example:
if let data = json.data(using: .utf8) {
let value = try! decodeAppleMusic(data: data)
print(value) // Applmusic(code: "AAPL", quality: "good", line: "She told me don\'t worry")
}
Recommended read:
Encoding and Decoding Custom Types
https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types
Interesting question. I know that it was 2 weeks ago but I was wondering
how it can be solved using library KeyedCodable I created. Here is my proposition with generic:
struct Response<Type>: Codable, Keyedable where Type: Codable {
var responseObject: Type!
mutating func map(map: KeyMap) throws {
try responseObject <-> map[map.userInfo.keyPath]
}
init(from decoder: Decoder) throws {
try KeyedDecoder(with: decoder).decode(to: &self)
}
}
helper extension:
private let infoKey = CodingUserInfoKey(rawValue: "keyPath")!
extension Dictionary where Key == CodingUserInfoKey, Value == Any {
var keyPath: String {
set { self[infoKey] = newValue }
get {
guard let key = self[infoKey] as? String else { return "" }
return key
}
}
use:
let decoder = JSONDecoder()
decoder.userInfo.keyPath = "applmusic"
let response = try? decoder.decode(Response<Applmusic>.self, from: jsonData)
Please notice that keyPath may be nested more deeply I mean it may be eg. "responseData.services.applemusic".
In addition Response is a Codable so you can encode it without any additional work.
I've got some JSON messages coming in over a websocket connection.
// sample message
{
type: "person",
data: {
name: "john"
}
}
// some other message
{
type: "location",
data: {
x: 101,
y: 56
}
}
How can I convert those messages into proper structs using Swift 4 and the Codable protocol?
In Go I can do something like: "Hey at the moment I only care about the type field and I'm not interested in the rest (the data part)." It would look like this
type Message struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
As you can see Data is of type json.RawMessage which can be parsed later on. Here is a full example https://golang.org/pkg/encoding/json/#example_RawMessage_unmarshal.
Can I do something similar in Swift? Like (haven't tried it yet)
struct Message: Codable {
var type: String
var data: [String: Any]
}
Then switch on the type to convert the dictionary into proper structs. Would that work?
I wouldn't rely upon a Dictionary. I'd use custom types.
For example, let's assume that:
you know which object you're going to get back (because of the nature of the request); and
the two types of response truly return identical structures except the contents of the data.
In that case, you might use a very simple generic pattern:
struct Person: Decodable {
let name: String
}
struct Location: Decodable {
let x: Int
let y: Int
}
struct ServerResponse<T: Decodable>: Decodable {
let type: String
let data: T
}
And then, when you want to parse a response with a Person, it would be:
let data = json.data(using: .utf8)!
do {
let responseObject = try JSONDecoder().decode(ServerResponse<Person>.self, from: data)
let person = responseObject.data
print(person)
} catch let parseError {
print(parseError)
}
Or to parse a Location:
do {
let responseObject = try JSONDecoder().decode(ServerResponse<Location>.self, from: data)
let location = responseObject.data
print(location)
} catch let parseError {
print(parseError)
}
There are more complicated patterns one could entertain (e.g. dynamic parsing of the data type based upon the type value it encountered), but I wouldn't be inclined to pursue such patterns unless necessary. This is a nice, simple approach that accomplishes typical pattern where you know the associated response type for a particular request.
If you wanted you could validate the type value with what was parsed from the data value. Consider:
enum PayloadType: String, Decodable {
case person = "person"
case location = "location"
}
protocol Payload: Decodable {
static var payloadType: PayloadType { get }
}
struct Person: Payload {
let name: String
static let payloadType = PayloadType.person
}
struct Location: Payload {
let x: Int
let y: Int
static let payloadType = PayloadType.location
}
struct ServerResponse<T: Payload>: Decodable {
let type: PayloadType
let data: T
}
Then, your parse function could not only parse the right data structure, but confirm the type value, e.g.:
enum ParseError: Error {
case wrongPayloadType
}
func parse<T: Payload>(_ data: Data) throws -> T {
let responseObject = try JSONDecoder().decode(ServerResponse<T>.self, from: data)
guard responseObject.type == T.payloadType else {
throw ParseError.wrongPayloadType
}
return responseObject.data
}
And then you could call it like so:
do {
let location: Location = try parse(data)
print(location)
} catch let parseError {
print(parseError)
}
That not only returns the Location object, but also validates the value for type in the server response. I'm not sure it's worth the effort, but in case you wanted to do so, that's an approach.
If you really don't know the type when processing the JSON, then you just need to write an init(coder:) that first parses the type, and then parses the data depending upon the value that type contained:
enum PayloadType: String, Decodable {
case person = "person"
case location = "location"
}
protocol Payload: Decodable {
static var payloadType: PayloadType { get }
}
struct Person: Payload {
let name: String
static let payloadType = PayloadType.person
}
struct Location: Payload {
let x: Int
let y: Int
static let payloadType = PayloadType.location
}
struct ServerResponse: Decodable {
let type: PayloadType
let data: Payload
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decode(PayloadType.self, forKey: .type)
switch type {
case .person:
data = try values.decode(Person.self, forKey: .data)
case .location:
data = try values.decode(Location.self, forKey: .data)
}
}
enum CodingKeys: String, CodingKey {
case type, data
}
}
And then you can do things like:
do {
let responseObject = try JSONDecoder().decode(ServerResponse.self, from: data)
let payload = responseObject.data
if payload is Location {
print("location:", payload)
} else if payload is Person {
print("person:", payload)
}
} catch let parseError {
print(parseError)
}
Here I am having value in JSON in which for some of multiple key value pairs it returning string and for some it is returning array here in custom attributes array in first dictionary in that value key value pair the data present is different and in the second dictionary value key value pair is different here then how to implement the model class for inside array for different key values ?
struct MediaGallery {
let id : Int
let mediaType : String
let label : Any
let position : Int
let disabled : Any
let file : String
init(dict : [String:Any]) {
self.id = (dict["id"] as? Int)!
self.mediaType = (dict["media_type"] as? String)!
self.label = dict["label"]!
self.position = (dict["position"] as? Int)!
self.disabled = dict["disabled"]!
self.file = (dict["file"] as? String)!
}
}
struct AttributeList {
let label : String
let value : String
let code : String
init(dict : [String:Any]){
self.label = (dict["label"])! as! String
self.value = (dict["value"])! as! String
self.code = (dict["code"])! as! String
}
}
struct DetailsListAttribute {
let attributeCode : String
let value : Any
init?(dict : [String:Any]) {
self.attributeCode = dict["attribute_code"] as! String
print(self.attributeCode)
if let values = dict["value"] as? String {
self.value = values
}
else {
if let arr = dict["value"] as? [[String:Any]]{
var filterArr = [AttributeList]()
for obj in arr {
filterArr.append(AttributeList(dict: obj))
}
self.value = filterArr
} else {
self.value = [AttributeList]()
}
}
}
}
I would suggest please save some time by using this great GIT Library ObjectMapper . it will help you to model your object and convert your model objects (classes and structs) to JSON and vice versa.
I've tried multiple JSON-mapping frameworks that were mentioned in Tj3n comment. They all have pros and cons. Apple suggests you to follow the recommendation given here. Also you should check Codable protocol (swift 4 is required).
Ok I don't have the whole JSON, and it doesn't seem clear to me.
But here is how you can parse and create your model Class easily in Swift with the Codable protocol.
You can read more about it and/or some examples, tutorials : Ultimate Guide.
Briefly, what is the Codable protocol ?
You don't need third party library anymore in order to parse and set the json data to your model class.
You juste have to create your class like the JSON is represented. And according to the key-name, it will create the class, properties and everything for you.
Here is an example with your JSON, I don't know if I understood your JSON formatting, but you got the trick :
struct Response: Codable {
let ca: [CustomAttribute]?
enum CodingKeys: String, CodingKey {
case ca = "custom_attributes"
}
}
struct CustomAttribute: Codable {
let code: String?
let value: [Value]?
struct Value: Codable {
let label: String?
let value: String?
let code: String?
let avg: String? // I don't know how your value array is composed
let count: Int? // I don't know how your value array is composed
}
enum CodingKeys: String, CodingKey {
case code = "attribute_code"
case avg = "avg_rating_percent"
}
}
For me, it looks like something like that.
I don't see the whole JSON, but imagine you have the whole JSON as the Response Struct, it contains several objects, like the CustomAttribute Array for example.
Then you can define the CustomAttribute structure, and add as many properties as the JSON has.
Anyway, you can call it this way :
When you have the response from your API call, you can go :
if let data = response.data {
let decoder = JSONDecoder()
let response = try! decoder.decode(Response.self, from: data)
print("Only printing the Custom Attribute : \(response.ca!)")
}
I decode the whole json data as an Object Response (like my Struct).
And I pass to my response callback, or
this might be late but I think this will helps others
The model class which are varies for frameworks like SwiftyJSON, simple swift class, Gloss or swift codable (Swift 4). you can easily generate model class online with your customization jsoncafe.com