I make HTTP requests to a server and I receive JSON documents.
I have the following structure to decode the JSON:
struct DocumenJSON: Codable {
let code: Int?
let description: String?
let value: Value?
}
The problem is that making a request "A" I receive an Object value and making the request "B" an Array of Value, so the struct should be the following:
struct DocumenJSONArray: Codable {
let code: Int?
let description: String?
let value: [Value]?
}
How can I implement this in swift 4 without duplicate code?
} catch let jsonErr {
print("Error serializing json:", jsonErr)
do {
document = try JSONDecoder().decode(DocumenJSON.self, from: data)
user = User.init(password: "", email: document?.value?.email ?? "Empty", givenNames: document?.value?.nickname ?? "Empty", familyName: document?.value?.lastname ?? "Empty", phone: document?.value?.nickname ?? "Empty")
} catch let jsonErr2 {
print("Error serializing json2:", jsonErr2)
}
}
A possible solution is to use a single struct, for instance:
extension DocumenJSON: Decodable {
enum CodingKeys: String, CodingKey {
case code
case description
case valueAsObject
case valueAsArray
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
code = try values.decodeIfPresent(Int.self, forKey: .code)
description = try values.decodeIfPresent(String.self, forKey: .description)
valueAsObject = try values.decodeIfPresent(Value.self, forKey: .valueAsObject)
valueAsArray = try values.decodeIfPresent(Array.self, forKey: .valueAsArray)
}
}
Now, whenever you call DocumenJSON just check for each property which valueAsObject or valueAsArray is nil.
Probably not the prettiest solution but at least you're not repeating structs.
Related
I have a JSON object with incrementing names to parse and I want to store the output into an object with a name field and a list of pet field. I normally use JSONDecoder as its pretty handy and easy to use, but I don't want to hard-code the CodingKey as I think it is very bad practice.
Input:
{"shopName":"KindHeartVet", "pet1":"dog","pet2":"hamster","pet3":"cat", ...... "pet20":"dragon"}
The object that I want to store the result in is something like the following.
class VetShop: NSObject, Decodable {
var shopName: String?
var petList: [String]?
private enum VetKey: String, CodingKey {
case shopName
case petList
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: VetKey.self)
shopName = try? container.decode(String.self, forKey: .shopName)
// implement storing of petList here.
}
}
What I'm struggling a lot on is, as CodingKey is enum, its a let constants, so I can't modify (and shouldn't modify) a constant, but I need to map the petList to the "petN" field, where N is the incrementing number.
EDIT :
I definitely cannot change the API response structure because it is a public API, not something I developed, I'm just trying to parse and get the value from this API, hope this clear the confusion!
Codable has provisions for dynamic keys. If you absolutely can't change the structure of the JSON you're getting, you could implement a decoder for it like this:
struct VetShop: Decodable {
let shopName: String
let pets: [String]
struct VetKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.stringValue = "\(intValue)";
self.intValue = intValue
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: VetKeys.self)
var pets = [String]()
var shopName = ""
for key in container.allKeys {
let str = try container.decode(String.self, forKey: key)
if key.stringValue.hasPrefix("pet") {
pets.append(str)
} else {
shopName = str
}
}
self.shopName = shopName
self.pets = pets
}
}
You can try this to parse your data as Dictionary. In this way, you can get all keys of the dictionary.
let url = URL(string: "YOUR_URL_HERE")
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
guard let data = data, error == nil else { return }
do {
let dics = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! Dictionary<String, Any>
let keys = [String](dics.keys)
print(keys) // You have the key list
print(dics[keys[0]]) // this will print the first value
} catch let error as NSError {
print(error)
}
}).resume()
I hope you can figure out what you need to do.
I have 2 types of response depending on my reuest: First one:
{
"status": "success"
"data": {
"user_id": 2,
"user_name": "John"
}
}
And second one is:
{
"status": "error",
"data": [],
}
I am using struct like that:
struct ValyutaListData:Decodable {
let status: String?
let data: [String]?
}
But if response is first type response, then an error occured. Because In first Type response data is not array. It is Json object. Then i use structure like that:
struct ValyutaListData:Decodable {
let status: String?
let data: Persondata?
}
struct Persondata: Decodable{
let user_id: Int?
let user_name: String?
}
If response is second type response, the error will be occured. What kind of of structure should use for dynamic type JSONs? Thanks.
One reasonable solution is an enum with associated type(s)
struct User : Decodable {
let userId: Int
let userName: String
}
enum Result : Decodable {
case success(User), failure
enum CodingKeys: String, CodingKey { case status, data }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let status = try container.decode(String.self, forKey: .status)
if status == "success" {
let userData = try container.decode(User.self, forKey: .data)
self = .success(userData)
} else {
self = .failure
}
}
}
And use it
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Result.self, from: data)
switch result {
case .success(let user): print(user)
case .failure: print("An error occurred")
}
} catch { print(error) }
I have trouble accessing a part of a JSON response.
(part of)The Source:
{
"time":1552726518,
"result":"success",
"errors":null,
"responce":{
"categories":{
"1":{
"nl":{
"name":"New born",
"description":"TEST",
"children":[
{
"name":"Unisex",
"description":"TESTe",
"children":[
{
"name":"Pants",
"description":"TEST",
"children":false
}
]
}
]
}
}
}
}
}
Approach
As you can see the source can have multiple categories. A single categorie will have a 'name', 'description' and may have 'children'. Children wil have a 'name', 'description' and also may have 'children'. This might go endless. If there are no children the SJON statement is 'false'
I use the website: https://app.quicktype.io to generate a junk of code to parse the JSON. I modified the result, because the website doesn't know that the children can go endless:
struct ProductCategories: Codable {
let time: Int?
let result: String?
let errors: [String]?
let responce: ProductCategoriesResponce?
init(time: Int? = nil, result: String? = nil, errors: [String]? = nil, responce: ProductCategoriesResponce? = nil) {
self.time = time
self.result = result
self.errors = errors
self.responce = responce
}
}
struct ProductCategoriesResponce: Codable {
let categories: [String: Category]?
}
struct Category: Codable {
let nl, en: Children?
}
struct Children: Codable {
let name, description: String?
let children: EnChildren?
}
enum EnChildren: Codable {
case bool(Bool)
case childArray([Children])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Bool.self) {
self = .bool(x)
return
}
if let x = try? container.decode([Children].self) {
self = .childArray(x)
return
}
throw DecodingError.typeMismatch(EnChildren.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for EnChildren"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .bool(let x):
try container.encode(x)
case .childArray(let x):
try container.encode(x)
}
}
}
And I can decode de data with:
productCategories = try JSONDecoder().decode(ProductCategories.self, from: jsonData!)
This part works fine. I can access "New Born", but can't get access his children.
I search a long time for the answer I tried so much. To much to all share here. What I expect to get access is:
if let temp = productCategories.responce?.categories?["\(indexPath.item)"]?.nl?.children! {
let x = temp(from: Decoder)
but this will trow me an error:
"Cannot call value of non-function type 'EnChildren'"
also code like:
let temp1 = productCategories.responce?.categories?["\(indexPath.item)"]?.nl?.children
Won't get me anywhere.
So, any ideas? Thank you.
First of all: If you are responsible for the server side, send null for the leaf children or omit the key rather than sending false. It makes the decoding process so much easier.
app.quicktype.io is a great resource but I disagree with its suggestion.
My solution uses a regular struct Children with a custom initializer. This struct can be used recursively.
The key children is decoded conditionally. First decode [Children], on failure decode Bool and set children to nil or hand over the error.
And I declared struct members as much as possible as non-optional
struct Response: Decodable {
let time: Date
let result: String
let errors: [String]?
let responce: ProductCategories
}
struct ProductCategories: Decodable {
let categories: [String: Category]
}
struct Category: Decodable {
let nl: Children
}
struct Children: Decodable {
let name, description: String
let children : [Children]?
private enum CodingKeys : String, CodingKey { case name, description, children }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
description = try container.decode(String.self, forKey: .description)
do {
children = try container.decode([Children].self, forKey: .children)
} catch DecodingError.typeMismatch {
_ = try container.decode(Bool.self, forKey: .children)
children = nil
}
}
}
In the root object the key time can be decoded to Date if you specify an appropriate date decoding strategy
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let result = try decoder.decode(Response.self, from: jsonData!)
And you can access for example the name Unisex with
result.responce.categories["1"]?.nl.children?[0].name
I'm calling an API and storing the data in an array.
But if there is no data the debugger says:
Expected to decode Array but found a dictionary instead.
The fail JSON response is:
'{"status":"Failed","msg":"Sorry It\'s Not Working"}'
The success JSON response is:
'[{"id":"509","name":"TEC TEST !"#!12","sortingId":"1"},
{"id":"510","name":"TEC TEST !"#!12","sortingId":"2"},
{"id":"511","name":"TEC TEST !"#!12","sortingId":"3"},
{"id":"512","name":"TEC TEST !"#!12","sortingId":"4"},
{"id":"513","name":"TEC TEST !"#!12","sortingId":"5"},
{"id":"514","name":"TEC TEST !"#!12","sortingId":"6"},
{"id":"519","name":"TEC TEST !"#!12","sortingId":"7"}]'
So I want to switch between fetching my response as
var result:[Items]?
and
var result:Items?
if the Failed JSON gets send
I've been google´ing and searching Stackoverflow without luck
Is there an solution to say if JSON is an array or dictionary?
My Struct:
struct Items: Codable {
let id: String?
let sortingId: String?
let name: String?
let response: String?
let status: String?
let msg: String?
}
My processing of the response:
var result:[Items]?
result = try JSONDecoder().decode([Items].self, from: data!)
DispatchQueue.main.async {
for item in result! {
self.itemArray.append((name: item.name!, id: Int(item.id!)!, sortingId: Int(item.sortingId!)!))
}
}
One solution is to write a custom initializer which conditionally decodes the array or the dictionary.
Please ask the owner of the service to send more consistent JSON. It's very bad. At least the object should be always a dictionary with key status and either the array for key items or the key msg.
This code first tries to decode the array with unkeyedContainer. If it fails it decodes the dictionary.
struct Item: Decodable {
let id: String
let sortingId: String
let name: String
}
struct ItemData : Decodable {
private enum CodingKeys: String, CodingKey { case status, msg }
let status : String?
let msg: String?
var items = [Item]()
init(from decoder: Decoder) throws {
do {
var unkeyedContainer = try decoder.unkeyedContainer()
while !unkeyedContainer.isAtEnd {
items.append(try unkeyedContainer.decode(Item.self))
}
status = nil; msg = nil
} catch DecodingError.typeMismatch {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decodeIfPresent(String.self, forKey: .status)
msg = try container.decodeIfPresent(String.self, forKey: .msg)
}
}
}
And call it
result = try JSONDecoder().decode(ItemData.self, from: data!)
A – probably more suitable – alternative is to catch the error in the JSONDecoder().decode line and use two simple structs
struct Item: Decodable {
let id: String
let sortingId: String
let name: String
}
struct ErrorData : Decodable {
let status : String
let msg: String
}
and call it
do {
let decoder = JSONDecoder()
do {
let result = try decoder.decode([Item].self, from: data!)
print(result)
} catch DecodingError.typeMismatch {
let result = try decoder.decode(ErrorData.self, from: data!)
print(result)
}
} catch { print(error) }
A big benefit is that all properties can be non-optional.
First of all the JSON does not contain any array. It's very very easy to read JSON. There are only 2 (two!) collection types, array [] and dictionary {}. As you can see there are no square brackets at all in the JSON string.
it will helpful
When I try to decode JSON I get the error:
(Error: typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [sweetyanime.Video.(CodingKeys in _D1045E05CDE474AEBA8BDCAF57455DC3).video, sweetyanime.iD.(CodingKeys in _D1045E05CDE474AEBA8BDCAF57455DC3).ID, sweetyanime.other.(CodingKeys in _D1045E05CDE474AEBA8BDCAF57455DC3).CountOfVideos], debugDescription: "Expected to decode String but found a dictionary instead.", underlyingError: nil)
1) JSON:
{ "video":{
"ID":{
"Name":"NameOfAnime",
"Describe":"SomeDescribe",
"Image":"https://firebasestorage.googleapis.com/v0/b/sweety-anime-e6bb4.appspot.com/o/main.png?alt=media&token=042a2dad-8519-4904-9ba3-262c2c962434",
"CountOfVideos":{
"1Series":"https://firebasestorage.googleapis.com/v0/b/sweety-anime-e6bb4.appspot.com/o/message_movies%252F12323439-9729-4941-BA07-2BAE970967C7.movalt=media&token=978d8b3a-7aad-468f-87d4-2b587d616720"
}
} } }
2) Swift code:
let jsonUrl = "file:///Users/tima/WebstormProjects/untitled/db.json"
guard let url = URL(string: jsonUrl) else { return }
URLSession.shared.dataTask(with: url) { (data, reponse, error) in
guard let data = data else {return}
do {
let video = try
JSONDecoder().decode(Video.self, from: data)
print(video.video.ID.Name)
} catch let jsonErr {
print("Error: ", jsonErr)
}
}.resume()
3) Video.swift
struct Video: Decodable {
private enum CodingKeys : String, CodingKey { case video = "video"
}
let video: iD
}
struct iD: Decodable {
private enum CodingKeys : String, CodingKey { case ID = "ID" }
let ID: other
}
struct other: Decodable {
private enum CodingKeys : String, CodingKey {
case Name = "Name"
case Describe = "Describe"
case Image = "Image"
case CountOfVideos = "CountOfVideos"
}
let Name: String
let Describe: String
let Image: String
let CountOfVideos: String
}
Let's put some line breaks in your error message to make it understandable:
(Error: typeMismatch(Swift.String,
Swift.DecodingError.Context(codingPath: [
sweetyanime.Video.(CodingKeys in _D1045E05CDE474AEBA8BDCAF57455DC3).video,
sweetyanime.iD.(CodingKeys in _D1045E05CDE474AEBA8BDCAF57455DC3).ID,
sweetyanime.other.(CodingKeys in _D1045E05CDE474AEBA8BDCAF57455DC3).CountOfVideos],
debugDescription: "Expected to decode String but found a dictionary instead.",
underlyingError: nil)
So it was trying to decode the value for “CountOfVideos”. It expected a String but it found a dictionary.
You need to either define a struct corresponding to the “CountOfVideos” dictionary (it appears to contain one key, “1Series”, with a string value), or you need to remove the CountOfVideos property from your other struct so you don't try to decode it at all.
My I think you should implement the init(from decoder) initializer that is part of Decodable. It gives a lot more flexibility on how to handle nested JSON than you get from the automatic implementation. One possible implementation would be something like:
struct Video: Decodable {
let name: String
let describe: String
let image: String
let countOfVideos: String
private enum VideoKey: String, CodingKey {
case video = "video"
}
private enum IDKey: String, CodingKey {
case id = "ID"
}
private enum CodingKeys: String, CodingKey {
case name = "Name"
case describe = "Describe"
case image = "Image"
case countOfVideos = "CountOfVideos"
}
private enum VideoCountKey: String, CodingKey {
case videoCount = "1Series"
}
init(from decoder: Decoder) throws {
let videoContainer = try decoder.container(keyedBy: VideoKey.self)
let idContainer = try videoContainer.nestedContainer(keyedBy: IDKey.self, forKey: .video)
let valuesContainer = try idContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .id)
self.name = try valuesContainer.decode(String.self, forKey: .name)
self.describe = try valuesContainer.decode(String.self, forKey: .describe)
self.image = try valuesContainer.decode(String.self, forKey: .image)
let videoCountContainer = try valuesContainer.nestedContainer(keyedBy: VideoCountKey.self, forKey: .countOfVideos)
self.countOfVideos = try videoCountContainer.decode(String.self, forKey: .videoCount)
}
This works with your example data in a Playground. I really don't know if you wanted the key or the value for your videoCount. (Neither looks like a count of videos to me. Generally, you just need to define a CodingKey enum for every level of your nested JSON and you can decode individual values as you see fit. (Note, since I've only seen one example of your data, this may break on some stuff in whatever API you're working with, so modify/take the ideas and rewrite it as needed)