Need to map the json to model in Swift - swift

I have a Json response that i get from a API, the json looks like something like this
{
"data": [
{
"_id": "63d9d2d57c0cfe791b2b19f6",
"step": {
"_id": "step1",
"status": "STARTED",
"children": [
{
"_id": "step2",
"status": "NOT_STARTED",
"children": [
{
"_id": "step3",
"status": "NOT_STARTED",
"children": [
{
"_id": "step3",
"status": "NOT_STARTED"
}
]
}
]
}
]
},
"status": "IN_PROGRESS",
"createdBy": "2700930039"
}
]
}
The json can have multiple levels of children objects inside each other. I need to map this json response to Models in swift
Here are the Models for the Json that i created
struct NestedJsonModel:Decodable {
var data:[LotData]
}
struct LotData:Decodable {
var _id: String
var step: StepDetails
var status: String
var createdBy: String
}
struct StepDetails:Decodable {
var _id: String
var status: String
var children: [ChildSteps]
}
struct ChildSteps:Decodable {
var _id: String
var status: String
var children: [StepDetails] //because children contains the same model as Step Details
}
Here is the decoder code
let jsonData = data.data(using: .utf8)!
do {
let decoder = JSONDecoder()
let tableData = try decoder.decode(NestedJsonModel.self, from: jsonData)
result = tableData.data
print("****************")
print(result!)
print("****************")
}
catch {
print (error)
}
But i keep getting this error
keyNotFound(CodingKeys(stringValue: "children", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "step", intValue: nil), CodingKeys(stringValue: "children", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "children", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"children\", intValue: nil) (\"children\").", underlyingError: nil))

Notice that the innermost layer of your JSON does not have a "children" key, but your ChildSteps struct does have a non-optional children property that needs to be assigned some value.
To fix this, you just need to make children have an optional type.
Also note that since StepDetails and ChildSteps have the same properties, you can merge them too.
struct NestedJsonModel:Decodable {
var data:[LotData]
}
struct LotData:Decodable {
var _id: String
var step: StepDetails
var status: String
var createdBy: String
}
struct StepDetails:Decodable {
var _id: String
var status: String
var children: [StepDetails]?
}
If you don't need to differentiate between "having an empty children array" and "not having the children key at all", then you can make the children property non-optional, and initialise it with an empty array when the key is not found.
This can be conveniently done without handwriting your own custom decoding logic by using a property wrapper as shown in this answer:
#propertyWrapper
struct DefaultEmptyArray<T:Decodable>: Decodable {
var wrappedValue: [T] = []
init() {}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode([T].self)
}
}
extension KeyedDecodingContainer {
func decode<T:Decodable>(_ type: DefaultEmptyArray<T>.Type,
forKey key: Key) throws -> DefaultEmptyArray<T> {
try decodeIfPresent(type, forKey: key) ?? .init()
}
}
// in StepDetails
#DefaultEmptyArray var children: [StepDetails]?

Related

swift can not make struct for parsing geoJSON

I have geoJSON file:
{
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
40.303141,
55.9765684
],
[
40.3033449,
55.9765114
],
[
40.3034017,
55.976575
],
[
40.3031979,
55.9766321
],
[
40.303141,
55.9765684
]
]
]
]
},
"properties": {
"#id": 4305947573,
"building": "yes"
}
}
I'm interested in properties:
"properties":{"#id":4305947573,"building":"yes"}
I want parse "properties", and make structure:
struct Feature: Decodable {
let type: String
let properties: Dictionary<String, String> }
It's work good, but then i add parameter in geoJSON: "#id":4305947573
4305947573 - this is Int variable, and parser don't parse geoJSON.
I think i need modify my struct Feature. I want to parser understand and String, and Int in properties.
Help me please. Thank you
There are a number of GeoJSON swift libraries (search github) that you could use
instead of re-inventing the wheel.
If you really want to code it yourself, try this approach,
where the dynamic keys and values of properties are decoded
into a dictionary of var data: [String: Any], as shown.
Use the struct models like this:
let result = try JSONDecoder().decode(Feature.self, from: data)
Models
struct Feature: Decodable {
let type: String
var properties: Properties
// ...
}
struct Properties: Decodable {
var data: [String: Any] = [:]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DynamicKey.self)
container.allKeys.forEach { key in
if let theString = try? container.decode(String.self, forKey: key) {
self.data[key.stringValue] = theString
}
if let theInt = try? container.decode(Int.self, forKey: key) {
self.data[key.stringValue] = theInt
}
}
}
}
struct DynamicKey: CodingKey {
var intValue: Int?
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = ""
}
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
}

SwiftUI - No value associated with key CodingKeys

I am currently trying to write a universal Request object in my SwiftUI application that aligns with an API I am building. I have written out something simple with the JSON payload that I have as an example and am running into an issue with the decoding of the object. Below is the Request object.
let json = """
{
"object": "bottle",
"has_more": false,
"data": [
{
"id": "5ffa81e7-1d91-43b5-83cc-9ee1ab634c7b",
"name": "14 Hands Hot to Trot White Blend",
"price": "$8.99",
"image": "https://cdn11.bigcommerce.com/s-7a906/images/stencil/1000x1000/products/8186/10996/14-Hands-Hot-to-Trot-White-Blend__56901.1488985626.jpg?c=2",
"sku": "088586004490",
"size": "750ML",
"origination": "USA, Washington",
"varietal": "White Wine",
"information": "14 Hands Hot to Trot White Blend",
"proof": 121.5,
"brand_id": "1",
"rating": 1,
"review_count": 5
}
]
}
"""
struct Request: Decodable {
let object: String
let has_more: Bool
let data: [RequestData]
}
struct Bottle: Decodable {
let id: String
let name: String
let price: String
let image: String
let sku: String
let size: String
let origination: String
let varietal: String
let information: String
let proof: Float
let brand_id: String
let rating: Int
let review_count: Int
}
enum ObjectType: String, Decodable {
case bottle
}
enum BottleData: Decodable {
case bottle(Bottle)
}
struct RequestData: Decodable {
let hasMore: String
let object: ObjectType
let innerObject: BottleData
enum CodingKeys: String, CodingKey {
case hasMore
case object
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.object = try container.decode(ObjectType.self, forKey: .object)
self.hasMore = try container.decode(String.self, forKey: .hasMore)
switch object {
case .bottle:
self.innerObject = .bottle(
try Bottle(from: decoder)
)
}
}
}
let decoder = JSONDecoder()
let requestData = try decoder.decode(Request.self, from: json.data(using: .utf8)!)
for data in requestData.data {
switch data.innerObject {
case .bottle(let bottle):
print(bottle.name)
}
}
I am recieving the following error when trying to test this object.
DecodingError
▿ keyNotFound : 2 elements
- .0 : CodingKeys(stringValue: "object", intValue: nil)
▿ .1 : Context
▿ codingPath : 2 elements
- 0 : CodingKeys(stringValue: "data", intValue: nil)
▿ 1 : _JSONKey(stringValue: "Index 0", intValue: 0)
- stringValue : "Index 0"
▿ intValue : Optional<Int>
- some : 0
- debugDescription : "No value associated with key CodingKeys(stringValue: \"object\", intValue: nil) (\"object\")."
- underlyingError : nil
Given that I only have one object at the moment, I'm a little confused as to why this is not working as I expect (without any errors). Can anybody spot what I may be doing wrong here?
Please read the error carefully.
The CodingPath components indicate the exact location of the error. It's
CodingKeys(stringValue: "object", intValue: nil)
CodingKeys(stringValue: "data", intValue: nil)
_JSONKey(stringValue: "Index 0", intValue: 0)
CodingKeys(stringValue: "object", intValue: nil).
which – translated to a key path – is
object.data[0].object
The actual error message
"No value associated with key CodingKeys(stringValue: "object", intValue: nil) ("object")."
states that there is no key object in the RequestData object which it is true.
I guess it's just a typo. Replace
let data: [RequestData]
with
let data: [Bottle]
and remove RequestData and the associated structs. They are pointless.
Edit:
You can do something like this, but as the JSON contains only one type the solution only decodes this particular JSON
let json = """
{
"object": "bottle",
"has_more": false,
"data": [
{
"id": "5ffa81e7-1d91-43b5-83cc-9ee1ab634c7b",
"name": "14 Hands Hot to Trot White Blend",
"price": "$8.99",
"image": "https://cdn11.bigcommerce.com/s-7a906/images/stencil/1000x1000/products/8186/10996/14-Hands-Hot-to-Trot-White-Blend__56901.1488985626.jpg?c=2",
"sku": "088586004490",
"size": "750ML",
"origination": "USA, Washington",
"varietal": "White Wine",
"information": "14 Hands Hot to Trot White Blend",
"proof": 121.5,
"brand_id": "1",
"rating": 1,
"review_count": 5
}
]
}
"""
enum RequestData {
case bottle([Bottle])
}
enum ObjectType: String, Decodable {
case bottle
}
struct Request: Decodable {
let object: ObjectType
let hasMore: Bool
let data: RequestData
enum CodingKeys: String, CodingKey { case hasMore = "has_more", object, data}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.object = try container.decode(ObjectType.self, forKey: .object)
self.hasMore = try container.decode(Bool.self, forKey: .hasMore)
switch object {
case .bottle:
let bottleData = try container.decode([Bottle].self, forKey: .data)
data = RequestData.bottle(bottleData)
}
}
}
struct Bottle: Decodable {
let id: String
let name: String
let price: String
let image: String
let sku: String
let size: String
let origination: String
let varietal: String
let information: String
let proof: Float
let brand_id: String
let rating: Int
let review_count: Int
}
let decoder = JSONDecoder()
let requestData = try decoder.decode(Request.self, from: Data(json .utf8))
print(requestData)

Swift - Constructing a base Decodable struct with Generics

So, I am trying to build a model that will be responsible for URLRequests and parsing Decodables. The response that is coming from the server is in the same form at the highest scope including keys status, page_count and results.
results values are changing respecting to the request, page_count is optional and status is just a String indicating whether the request was successful or not.
I tried to implement Generics to method itself and base Decodable struct named APIResponse and below is an example of just one endpoint, named extList. The code compiles, however in the runtime it throws
Thread 7: Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "results",
intValue: nil), Swift.DecodingError.Context(codingPath: [],
debugDescription: "No value associated with key
CodingKeys(stringValue: \"results\", intValue: nil) (\"results\").",
underlyingError: nil))
at the line of json = try! JSONDecoder().decode(APIResponse<T>.self, from: data)
class NetworkManager {
typealias completion<T: Decodable> = (Result<APIResponse<T>, Error>)->()
class func make<T: Decodable>(of type: T.Type,
request: API,
completion: #escaping completion<T>){
let session = URLSession.shared
var req = URLRequest(url: request.url)
req.httpMethod = request.method
req.httpBody = request.body
session.dataTask(with: req) { (data, response, error) in
if let error = error {
completion(.failure(error))
return
}
var json: APIResponse<T>
if let data = data,
let response = response as? HTTPURLResponse?,
response?.statusCode == 200 {
switch request {
case .extList:
json = try! JSONDecoder().decode(APIResponse<T>.self, from: data)
default:
return
}
completion(.success(json))
}
}.resume()
}
}
Here is the base struct
struct APIResponse<T: Decodable>: Decodable {
var status: String
var page_count: Int?
var results: T
}
Here is the response that should fill the results key in the APIResponse for this endpoint.
struct UserResponse: Decodable {
var name: String
var extens: Int
}
I am making my request as NetworkManager.make(of: [UserResponse].self, request: .extList) { (result) in ; return } and it works when I discard the Response generic type in the APIResponse with the Array<UserResponse> directly.
As requested, sample json I am trying to decode
{
"status": "ok-ext_list",
"page_count": 1,
"results": [
{
"name": "some name",
"extens": 249
},
{
"name": "some other name",
"extens": 245
}
]
}
Any ideas to fix this?
MINIMAL REPRODUCIBLE EXAMPLE
So the below code is working and I absolutely do not know why.
JSON's
import Foundation
var extListJSON : Data {
return try! JSONSerialization.data(withJSONObject: [
"status": "ok_ext-list",
"page_count": 1,
"results": [
[
"name": "some name",
"extens": 256
],
[
"name": "some other name",
"extens": 262
]
]
], options: .fragmentsAllowed)
}
var extListString: Data {
return """
{
"status": "ok-ext_list",
"page_count": 1,
"results": [
{
"name": "some name",
"extens": 249
},
{
"name": "some other name",
"extens": 245
}
]
}
""".data(using: .utf8)!
}
Manager and Service
enum Service {
case extList
}
class NetworkManager {
typealias completion<T: Decodable> = (APIResponse<T>) -> ()
class func make<T: Decodable>(of type: T.Type, request: Service, completion: completion<T>) {
let data = extListString
let json: APIResponse<T>
switch request {
case .extList:
json = try! JSONDecoder().decode(APIResponse<T>.self, from: data)
}
completion(json)
}
}
Decodables
struct APIResponse<T: Decodable>: Decodable {
var status: String
var page_count: Int?
var results: T
}
struct UserResponse: Decodable {
var name: String
var extens: Int
}
Finally method call
NetworkManager.make(of: [UserResponse].self, request: .extList) { (result) in
dump(result)
}
Again, I have no clue why this is working. I just removed the networking part and it started to work. Just a reminder that my original code is working as well if I just use seperate Decodable for each request -without using Generic struct-. Generic make(:_) is working fine as well.

Codable: Expected to decode Array<Any> but found a dictionary instead

I'm new to Codable and been playing around it today.
My current JSON model look like this:
{
"status": 200,
"code": 200,
"message": {
"1dHZga0QV5ctO6yhHUhy": {
"id": "23",
"university_location": "Washington_DC",
"docID": "1dHZga0QV5ctO6yhHUhy"
},
"0dbCMP7TrTEnpRbEleps": {
"id": "22",
"university_location": "Timber Trails, Nevada",
"docID": "0dbCMP7TrTEnpRbEleps"
}
}
}
However, Trying to decode this response with:
struct USA: Codable
{
//String, URL, Bool and Date conform to Codable.
var status: Int
var code: Int
// Message Data
var message: Array<String>
}
Gives out:
Expected to decode Array but found a dictionary instead.
Updating the message to Dictionary<String,String produces:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "message", intValue: nil), _JSONKey(stringValue: "1dHZga0QV5ctO6yhHUhy", intValue: nil)], debugDescription: "Expected to decode String but found a dictionary instead.", underlyingError: nil))
message key is a dictionary not an array
struct Root: Codable {
let status, code: Int
let message: [String: Message]
}
struct Message: Codable {
let id, universityLocation, docID: String
}
do {
let dec = JSONDecoder()
dec.keyDecodingStrategy = .convertFromSnakeCase
let res = try dec.decode(Root.self, from: data)
}
catch{
print(error)
}

Codable. How decode dictionary to property [duplicate]

How does the Swift 4 Decodable protocol cope with a dictionary containing a key whose name is not known until runtime? For example:
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
Here we have an array of dictionaries; the first has keys categoryName and Trending, while the second has keys categoryName and Comedy. The value of the categoryName key tells me the name of the second key. How do I express that using Decodable?
The key is in how you define the CodingKeys property. While it's most commonly an enum it can be anything that conforms to the CodingKey protocol. And to make dynamic keys, you can call a static function:
struct Category: Decodable {
struct Detail: Decodable {
var category: String
var trailerPrice: String
var isFavorite: Bool?
var isWatchlist: Bool?
}
var name: String
var detail: Detail
private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }
static let name = CodingKeys.make(key: "categoryName")
static func make(key: String) -> CodingKeys {
return CodingKeys(stringValue: key)!
}
}
init(from coder: Decoder) throws {
let container = try coder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.detail = try container.decode([Detail].self, forKey: .make(key: name)).first!
}
}
Usage:
let jsonData = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
}
]
""".data(using: .utf8)!
let categories = try! JSONDecoder().decode([Category].self, from: jsonData)
(I changed isFavourit in the JSON to isFavourite since I thought it was a mispelling. It's easy enough to adapt the code if that's not the case)
You can write a custom struct that functions as a CodingKeys object, and initialize it with a string such that it extracts the key you specified:
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
Thus, once you know what the desired key is, you can say (in the init(from:) override:
let key = // whatever the key name turns out to be
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
So what I ended up doing is making two containers from the decoder — one using the standard CodingKeys enum to extract the value of the "categoryName" key, and another using the CK struct to extract the value of the key whose name we just learned:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
Here, then, is my entire Decodable struct:
struct ResponseData : Codable {
let categoryName : String
let unknown : [Inner]
struct Inner : Codable {
let category : String
let trailerPrice : String
let isFavourit : String?
let isWatchList : String?
}
private enum CodingKeys : String, CodingKey {
case categoryName
}
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
}
And here's the test bed:
let json = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
"""
let myjson = try! JSONDecoder().decode(
[ResponseData].self,
from: json.data(using: .utf8)!)
print(myjson)
And here's the output of the print statement, proving that we've populated our structs correctly:
[JustPlaying.ResponseData(
categoryName: "Trending",
unknown: [JustPlaying.ResponseData.Inner(
category: "Trending",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)]),
JustPlaying.ResponseData(
categoryName: "Comedy",
unknown: [JustPlaying.ResponseData.Inner(
category: "Comedy",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)])
]
Of course in real life we'd have some error-handling, no doubt!
EDIT Later I realized (in part thanks to CodeDifferent's answer) that I didn't need two containers; I can eliminate the CodingKeys enum, and my CK struct can do all the work! It is a general purpose key-maker:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CK.self)
self.categoryName = try! con.decode(String.self, forKey:CK(stringValue:"categoryName")!)
let key = self.categoryName
self.unknown = try! con.decode([Inner].self, forKey: CK(stringValue:key)!)
}
Here's what I eventually came up for this json:
let json = """
{
"BTC_BCN":{
"last":"0.00000057",
"percentChange":"0.03636363",
"baseVolume":"47.08463318"
},
"BTC_BELA":{
"last":"0.00001281",
"percentChange":"0.07376362",
"baseVolume":"5.46595029"
}
}
""".data(using: .utf8)!
We make such a structure:
struct Pair {
let name: String
let details: Details
struct Details: Codable {
let last, percentChange, baseVolume: String
}
}
then decode:
if let pairsDictionary = try? JSONDecoder().decode([String: Pair.Details].self, from: json) {
var pairs: [Pair] = []
for (name, details) in pairsDictionary {
let pair = Pair(name: name, details: details)
pairs.append(pair)
}
print(pairs)
}
It is also possible to call not pair.details.baseVolume, but pair.baseVolume:
struct Pair {
......
var baseVolume: String { return details.baseVolume }
......
Or write custom init:
struct Pair {
.....
let baseVolume: String
init(name: String, details: Details) {
self.baseVolume = details.baseVolume
......