How to create model for this json with codable - swift

I have the below json and I want to create model for the json with codable.
{
id = 1;
name = "abc";
empDetails = {
data = [{
address = "xyz";
ratings = 2;
"empId" = 6;
"empName" = "def";
}];
};
}
Model
struct Root: Codable {
let id: Int
let name: String
let empDetails:[Emp]
struct Emp: Codable {
let address: String
let ratings: Int
let empId: Int
let empName: String
}
}
I don't need the key data. I want to set the value of data to empDetails property
How can I do this with init(from decoder: Decoder) throws method?

Simply create enum CodingKeys and implement init(from:) in struct Root to get that working.
struct Root: Decodable {
let id: Int
let name: String
let empDetails: [Emp]
enum CodingKeys: String, CodingKey {
case id, name, empDetails, data
}
struct Emp: Codable {
let address: String
let ratings: Int
let empId: Int
let empName: String
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
let details = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .empDetails)
empDetails = try details.decode([Emp].self, forKey: .data)
}
}

Related

Swift: How to automatically generate corresponding enum when use Codable

struct User: Codable {
var name: String
var createdAt: Date
var updatedAt: Date
var githubId: Int
}
extension User {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.createdAt = try container.decode(Date.self, forKey: .createdAt)
self.updatedAt = try container.decode(Date.self, forKey: .updatedAt)
self.githubId = try container.decode(Int.self, forKey: .githubId)
}
}
when I called container.decode, the key is like a enum, which name corresponds to the property name, how to implement this feature.

How to encode enum with Arrays of Custom Objects

Thanks for your help in advance! I am relatively new to SwiftUI and have been struggling with encoding an enum with Arrays of custom objects. Here is the code:
struct ChartData: Codable {
var data: DataType
...
private enum CodingKeys : String, CodingKey { case id, title, type, info, label_x, label_y, last_update, data }
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
...
try container.encode(self.data, forKey: .data)
}
}
enum DataType: Codable{
case plot([ChartPlotData])
case slice([ChartSliceData])
case number([ChartNumberData])
}
struct ChartPlotData: Codable {
var chart_id: String
var order_plot: Int
var label_plot: String?
var points_x: [String]
var points_y: [Double]
private enum CodingKeys : String, CodingKey { case chart_id, order_plot, label_plot, points_x, points_y }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.chart_id = try container.decode(String.self, forKey: .chart_id)
self.order_plot = try container.decode(Int.self, forKey: .order_plot)
self.label_plot = try? container.decode(String.self, forKey: .label_plot)
do{
self.points_x = try container.decode([String].self, forKey:.points_x)
}catch{
let xs = try container.decode([Double].self, forKey:.points_x)
self.points_x = xs.map { String($0) }
}
self.points_y = try container.decode([Double].self, forKey:.points_y)
}
}
struct ChartSliceData: Codable {
var chart_id: String
var order_slice: Int
var label_slice: String
var value_slice: Double
}
struct ChartNumberData: Codable {
var chart_id: String
var number: Double
var unit: String?
}
I am attempting to cache JSON in UserDefaults so as to avoid having to make extraneous API calls. Using JSONEncoder, I am left with the following JSON snippet (excerpted from a much longer string):
"data" : {
"plot" : {
"_0" : [
{
"label_plot" : "China",
"points_y" : [
0,
0,
0,
...
However, I am looking to get an encoding like this:
"data" : [
{
"label_plot" : "China",
"points_y" : [
0,
0,
0,
...
Any help would be greatly appreciated! Thanks so much!
This can be solved by adding an extra item in the CodingKeys enum for encoding and decoding the type used for the DataType enum and then using a switch to encode and decode the right type of array.
Here is the full ChartData struct although with some properties and code removed for brevity
struct ChartData: Codable {
var id: Int
var title: String
var data: DataType
enum CodingKeys: String, CodingKey {
case id, title, data, type
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(title, forKey: .title)
switch data {
case .number(let values):
try container.encode("number", forKey: .type)
try container.encode(values, forKey: .data)
case .plot(let values):
try container.encode("plot", forKey: .type)
try container.encode(values, forKey: .data)
case .slice(let values):
try container.encode("slice", forKey: .type)
try container.encode(values, forKey: .data)
}
}
init(from decoder: Decoder) throws {
var container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
title = try container.decode(String.self, forKey: .title)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "number":
let values = try container.decode([ChartNumberData].self, forKey: .data)
data = .number(values)
// case ...
default:
fatalError("Unsupported type for DataType: \(type)")
}
}
}

Decode to PropertyWrapper from JSON

I want to decode the JSON string into People as below. The age is number(Int) type, and the below code get error:
"Expected to decode Dictionary<String, Any> but found a number instead."
I think it means the #Age was treated as Dictionary<String, Any>.
Any way to decode JSON value to PropertyWrapper property?
let jsonString =
"""
{
"name": "Tim",
"age": 28
}
"""
#propertyWrapper
struct Age: Codable {
var age: Int = 0
var wrappedValue: Int {
get {
return age
}
set {
age = newValue * 10
}
}
}
struct People: Codable {
var name: String
#Age var age: Int
}
let jsonData = jsonString.data(using: .utf8)!
let user = try! JSONDecoder().decode(People.self, from: jsonData)
print(user.name)
print(user.age)
Thanks to the comment, add this make it works.
extension People {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
age = try values.decode(Int.self, forKey: .age)
}
}

Failable Initializers with Codable

I'm attempting to parse the following json schema of array of items, itemID may not be empty. How do I make an item nil id itemID does not exist in the JSON?
[{
"itemID": "123",
"itemTitle": "Hello"
},
{},
...
]
My decodable classes are as follows:
public struct Item: : NSObject, Codable {
let itemID: String
let itemTitle: String?
}
private enum CodingKeys: String, CodingKey {
case itemID
case itemTitle
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
itemID = try container.decode(String.self, forKey: .itemID)
itemTitle = try container.decodeIfPresent(String.self, forKey: .itemTitle)
super.init()
}
}
First of all, itemID is an Int and not String in your JSON response. So the struct Item looks like,
public struct Item: Codable {
let itemID: Int?
let itemTitle: String?
}
Parse the JSON like,
if let data = data {
do {
let items = try JSONDecoder().decode([Item].self, from: data).filter({$0.itemID == nil})
print(items)
} catch {
print(error)
}
}
In the above code you can simply filter out the items with itemID == nil.

Swift Codable - decode nested dictionary

Lets say I have a dictionary like this:
{"user_data":{"id":3,"name":"Damian D","email":"aaa#aaa.pl"},"status":true}
How can I use Codable protocol to decode just user_data into such struct:
struct User: Codable {
private enum CodingKeys: String, CodingKey {
case id
case username = "name"
case email
}
let id: Int
let username: String
let email: String
}
Do I need to convert this sub dictionary into Data, or is there a easier way?
If you create nested Coding Keys you will accomplish decoding the response using only one data model.
Given the following JSON response:
let data = """
{
"user_data": {
"id":3,
"name":"Damian D",
"email":"aaa#aaa.pl"
},
"status":true
}
""".data(using: .utf8, allowLossyConversion: false)!
and the following data model:
public struct User: Decodable {
var id: Int
var name: String
var email: String
// MARK: - Codable
private enum RootCodingKeys: String, CodingKey {
case userData = "user_data"
enum NestedCodingKeys: String, CodingKey {
case id
case name
case email
}
}
required public init(from decoder: Decoder) throws {
let rootContainer = try decoder.container(keyedBy: RootCodingKeys.self)
let userDataContainer = try rootContainer.nestedContainer(keyedBy: RootCodingKeys.NestedCodingKeys.self, forKey: .userData)
self.id = try userDataContainer.decode(Int.self, forKey: .id)
self.name = try userDataContainer.decode(String.self, forKey: .name)
self.email = try userDataContainer.decode(String.self, forKey: .email)
}
}
You can decode your response into a single object:
let decoder = JSONDecoder()
let user = try? decoder.decode(User.self, from: data)
Create a new struct that has a userData member of type User.
struct Response: Codable {
private enum CodingKeys: String, CodingKey {
case userData = "user_data"
case status
}
let userData: User
let status: Bool
}