Swift Codable Dictionary - json

I'm having an issue getting codable going. Any help would greatly appreciated. I have the following in my playground
Sample from my JSON file. It has many more elements, reduced it a smaller subset.
{
"metadata" : {
"generated" : {
"timestamp" : 1549331723,
"date" : "2019-02-04 20:55:23"
}
},
"data" : {
"CA" : {
"country-id" : 25000,
"country-iso" : "CA",
"country-eng" : "Canada",
"country-fra" : "Canada",
"date-published" : {
"timestamp" : 1544561785,
"date" : "2018-12-11 15:56:25",
"asp" : "2018-12-11T15:56:25.4141468-05:00"
}
},
"BM" : {
"country-id" : 31000,
"country-iso" : "BM",
"country-eng" : "Bermuda",
"country-fra" : "Bermudes",
"date-published" : {
"timestamp" : 1547226095,
"date" : "2019-01-11 12:01:35",
"asp" : "2019-01-11T12:01:35.4748399-05:00"
}
}
}
}
From The quicktype app. It generated a dictionary for Datum. The way the json is structured, the country abbreviation doesn't have a tag.
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let metadata: Metadata?
let data: [String: Datum]?
}
// MARK: - Datum
struct Datum: Codable {
let countryID: Int?
let countryISO, countryEng, countryFra: String?
let datePublished: DatePublished?
enum CodingKeys: String, CodingKey {
case countryID = "country-id"
case countryISO = "country-iso"
case countryEng = "country-eng"
case countryFra = "country-fra"
case datePublished = "date-published"
}
}
// MARK: - DatePublished
struct DatePublished: Codable {
var timestamp: Int
var date, asp: String
}
// MARK: - Metadata
struct Metadata: Codable {
var generated: Generated
}
// MARK: - Generated
struct Generated: Codable {
var timestamp: Int
var date: String
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) {
// No-op
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
From my code, I can load the json file, I'm not sure how to process the data here with the dictionary, and the country not having a name for the country abbreviation.
guard let url = Bundle.main.url(forResource: "data", withExtension: "json") else { return 0 }
let jsonData = try Data(contentsOf: url)
Note: This is a follow up to my earlier question: Swift Codable Parsing keyNotFound

Your data models are already defined correctly (however, I'd suggest some name changes and removing mutability/optionality from the properties).
Once you've parsed the JSON, there's no need to keep the Dictionary, since the keys are actually part of the value under the country-iso key.
So once you decoded your Root object, I would suggest simply keeping root.data.values, which gives you Array<CountryData>, which you can handle easily afterwards.
struct Root: Codable {
let data: [String: CountryData]
}
struct CountryData: Codable {
let countryID: Int
let countryISO, countryEng, countryFra: String
let datePublished: DatePublished
enum CodingKeys: String, CodingKey {
case countryID = "country-id"
case countryISO = "country-iso"
case countryEng = "country-eng"
case countryFra = "country-fra"
case datePublished = "date-published"
}
}
// MARK: - DatePublished
struct DatePublished: Codable {
let timestamp: Int
let date, asp: String
}
do {
let root = try JSONDecoder().decode(Root.self, from: countryJson.data(using: .utf8)!)
let countries = root.data.values
print(countries)
} catch {
error
}

Related

swift decode json dictionary api call [duplicate]

I am trying to parse the below JSON response, which has multiple dynamic keys,
{
"Nagaland": {
"districtData": {
"Dimapur": {
"confirmed": 1,
"lastupdatedtime": "",
"delta": {
"confirmed": 0
}
}
}
},
"Meghalaya": {
"districtData": {
"East Khasi Hills": {
"confirmed": 1,
"lastupdatedtime": "",
"delta": {
"confirmed": 0
}
}
}
}
}
I have written my Codable struct like below,,
struct IndianStateListModel: Codable {
// MARK: Properties
let state: [String: StateData]
}
struct StateData: Codable {
// MARK: Properties
var districtData: Inner?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case districtData
}
}
struct Inner: Codable {
// MARK: Properties
let districts: [String: DistrictData]
}
struct DistrictData: Codable {
// MARK: Properties
var confirmed: Int?
var lastupdatedtime: String?
var delta: DailyConfirmedData?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case confirmed, lastupdatedtime, delta
}
}
struct DailyConfirmedData: Codable {
// MARK: Properties
var confirmed: Int?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case confirmed
}
}
It's called as,
let summary = try JSONDecoder().decode(IndianStateListModel.self, from: data)
But its returning nil
P.S.: related question regarding decodable Swift Codable with dynamic keys
Any solution, would be great, Thanks in advance
The Codable models that you must use to parse the above JSON data should be like,
Models:
struct StateData: Codable {
var districtData: [String:DistrictData]?
}
struct DistrictData: Codable {
var confirmed: Int?
var lastupdatedtime: String?
var delta: DailyConfirmedData?
}
struct DailyConfirmedData: Codable {
var confirmed: Int?
}
Parsing:
let summary = try JSONDecoder().decode([String:StateData].self, from: data)
Note: There is no need to explicitly create enum CodingKeys if the JSON keys exactly match the properties of the Codable type.
The fundamental issue is that IndianStateListModel has a property called states. But no such key appears in your JSON. I’d suggest parsing it with singleValueContainer. E.g. perhaps:
struct States: Decodable {
typealias StateName = String
let states: [StateName: Districts]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
states = try container.decode([StateName: Districts].self)
}
}
struct Districts: Decodable {
typealias DistrictName = String
var districts: [DistrictName: DistrictData]
enum CodingKeys: String, CodingKey {
case districts = "districtData"
}
}
struct DistrictData: Decodable {
var confirmed: Int
var lastupdatedtime: String
var delta: DailyConfirmedData
}
struct DailyConfirmedData: Decodable {
var confirmed: Int?
}
And
do {
let result = try JSONDecoder().decode(States.self, from: data)
print(result)
} catch {
print(error)
}

Unable to GET from JSON API

I have tried following a variety of tutorials, and I am unable to progress on getting data from this API. I did manage to succeed on a simpler JSON ], but this one is eating up my time.
First, the JSON:
{
"object": {
"array": [
{
"id": 48,
"name": "Job No.# 48",
"description": "blah",
"start_at": "2021-03-05T13:15:00.000+11:00",
"end_at": "2021-03-05T14:15:00.000+11:00",
"map_address": "blah road"
},
{
"id": 56,
"name": "Job No.# 56",
"description": "Do it",
"start_at": "2021-06-22T11:30:00.000+10:00",
"end_at": "2021-06-22T13:30:00.000+10:00",
"map_address": " blah"
}
],
"person": {
"id": 52,
"first_name": "Bob",
"last_name": "Newby",
"mobile": "0401111111",
"email": "bob#mail.com"
}
}
}
And now my attempt at decoding it:
struct api_data: Codable {
let object : Object
}
struct Object: Codable {
let array : [array]
let person : Person
}
struct array: Codable, Identifiable {
let id : Int?
let start_at, end_at : Date?
let duration : Float?
let cancellation_type : String?
let name, description, address, city, postcode, state : String?
}
struct Person: Codable, Identifiable {
let id : Int?
let first_name, last_name, mobile, email : String?
}
class FetchShifts: ObservableObject {
#Published var shifts = [Shifts]()
init() {
let url = URL(string: "realURLhiddenForPrivacy")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("myToken", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) {(data, response, error) in
do {
if let array_data = data {
let array_data = try JSONDecoder().decode([array].self, from: array_data)
DispatchQueue.main.async {
self.array = array_data
}
} else {
print("No data")
}
} catch {
print(error)
}
}.resume()
}
}
And how I attempt to present it:
#ObservedObject var fetch = FetchArray()
var body: some View {
VStack {
List(array.shifts) { shft in
VStack(alignment: .leading) {
Text(shft.name!)
}
}
}
}
}
}
Any help is appreciated, not sure where it is I go wrong here, been at it for 5-7 hours going through tutorials.
I always recommend using app.quicktype.io to generate models from JSON if you're unfamiliar with it. Here's what it yields:
// MARK: - Welcome
struct Welcome: Codable {
let status: String
let payload: Payload
}
// MARK: - Payload
struct Payload: Codable {
let shifts: [Shift]
let worker: Worker
}
// MARK: - Shift
struct Shift: Codable, Identifiable {
let id: Int
let name, shiftDescription, startAt, endAt: String
let mapAddress: String
enum CodingKeys: String, CodingKey {
case id, name
case shiftDescription = "description"
case startAt = "start_at"
case endAt = "end_at"
case mapAddress = "map_address"
}
}
// MARK: - Worker
struct Worker: Codable {
let id: Int
let firstName, lastName, mobile, email: String
enum CodingKeys: String, CodingKey {
case id
case firstName = "first_name"
case lastName = "last_name"
case mobile, email
}
}
Then, to decode, you'd do:
do {
let decoded = try JSONDecoder().decode(Welcome.self, from: shift_data)
let shifts = decoded.payload.shifts
} catch {
print(error)
}
Note that in Swift, it's common practice to use camel case for naming, not snake case, so you'll see that CodingKeys does some conversion for that (there are automated ways of doing this as well).
Update, based on comments:
Your code would be:
if let shiftData = data {
do {
let decoded = try JSONDecoder().decode(Welcome.self, from: shiftData)
DispatchQueue.main.async {
self.shifts = decoded.payload.shifts
}
} catch {
print(error)
}
}
Instead of defining custom keys, you can automatically use
keyDecodingStrategy = .convertFromSnakeCase for your JSON decoder, and could specify custom date format or even throw a custom error in your decoder implementation.
struct Worker: Codable {
let id: Int
let firstName: String?
let lastName: String?
let mobile: String?
let email: String?
}
struct Shift: Codable, Identifiable {
let id: Int
let name: String?
let description: String?
let startAt: Date?
let endAt: Date?
let mapAddress: String?
}
struct Payload: Codable {
let shifts: [Shift]?
let worker: Worker?
}
struct Response: Codable {
let status: String
let payload: Payload?
}
class MyCustomDecoder: JSONDecoder {
override init() {
super.init()
self.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
self.dateDecodingStrategy = .formatted(dateFormatter)
}
}
// Usage
if let data = json.data(using: .utf8) {
let response = try MyCustomDecoder().decode(Response.self, from: data)
print(response)
}

JSONSerializer for different json object

I have different type of dataset in the given json.
Courses json
{"name" : "University of Florida",
"data": {"GivenCourse" :{"name" : "Introduction to Computer Science", "tuition": 3000}},
"type" : "Courses" }
Professors json
{"name" : "University of Florida",
"data": {"Professor" :{"name" : "Dr.Francis Tudeluv", "age" :53}},
"type" : "Professor" }
I could able to write two structs, one is for Professor and the other one is Courses. However, as you see there are several common elements in the json object except the data. How that could be better handled?
Courses Struct is as follows:
struct Courses: Codable {
let name: String
let data: DataClass
let type: String
}
// MARK: - DataClass
struct DataClass: Codable {
let givenCourse: GivenCourse
enum CodingKeys: String, CodingKey {
case givenCourse = "GivenCourse"
}
}
// MARK: - GivenCourse
struct GivenCourse: Codable {
let name: String
let tuition: Int
}
Professors Class
// MARK: - Welcome
struct Welcome: Codable {
let name: String
let data: DataClass
let type: String
}
// MARK: - DataClass
struct DataClass: Codable {
let professor: Professor
enum CodingKeys: String, CodingKey {
case professor = "Professor"
}
}
// MARK: - Professor
struct Professor: Codable {
let name: String
let age: Int
}
My suggestion is to declare DataClass as enum with associated types
enum DataClass {
case course(Course), professor(Professor)
}
and Type as decodable enum
enum Type : String, Decodable {
case courses = "Courses", professor = "Professor"
}
Then implement init(from decoder and decode the different types depending on the type value. The keys GivenCourse and Professor are ignored.
struct Root: Decodable {
let name: String
let data: DataClass
let type: Type
private enum CodingKeys : String, CodingKey { case name, data, type }
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.type = try container.decode(Type.self, forKey: .type)
switch type {
case .courses: let courseData = try container.decode([String:Course].self, forKey: .data)
data = .course(courseData.values.first!)
case .professor: let professorData = try container.decode([String:Professor].self, forKey: .data)
data = .professor(professorData.values.first!)
}
}
}
// MARK: - Professor
struct Professor: Decodable {
let name: String
let age: Int
}
// MARK: - Course
struct Course: Decodable {
let name: String
let tuition: Int
}
let jsonString = """
{"name" : "University of Florida",
"data": {"GivenCourse" :{"name" : "Introduction to Computer Science", "tuition": 3000}},
"type" : "Courses" }
"""
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(Root.self, from: data)
print(result)
} catch {
print(error)
}
If the possible types of "data" will remain fairly small, I would just use optionals for each possible type as concrete members of DataClass, with the CodingKeys fully specified, like so:
struct Professor: Codable {
let name: String
let age: Int
}
struct GivenCourse: Codable {
let name: String
let tuition: Int
}
struct DataClass: Codable {
var professor: Professor?
var givenCourse: GivenCourse?
enum CodingKeys: String, CodingKey {
case givenCourse = "GivenCourse"
case professor = "Professor"
}
}
struct SchoolData: Codable {
let name: String
let data: DataClass
}
This will correctly parse the two JSON samples you have, leaving the unused type nil in the DataClass. You could add some computed variables to DataClass if you like for convenient semantics, for example
var isProfessor: Bool {
get {
return nil != self.professor
}
}
This structure also lets you handle the case (if it's possible) of a record having both course and professor data - the DataClass will be able to parse both in one JSON record if it finds it.
Using generic and manual encode/decode:
struct MyDataContainer<T: Codable>: Codable {
let name: String
let data: T
let type: String
}
struct Professor: Codable {
let name: String
let age: Int
}
enum DataKind {
case unknown
case professor(Professor)
case givenCourse(GivenCourse)
}
struct GivenCourse: Codable {
let name: String
let tuition: Int
}
// MARK: - DataClass
struct DataClass: Codable {
let dataKind: DataKind
enum CodingKeys: String, CodingKey {
case professor = "Professor"
case givenCourse = "GivenCourse"
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if values.contains(.givenCourse) {
let course = try values.decode(GivenCourse.self, forKey: .givenCourse)
dataKind = .givenCourse(course)
} else if values.contains(.professor) {
let professor = try values.decode(Professor.self, forKey: .professor)
dataKind = .professor(professor)
} else {
dataKind = .unknown
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch dataKind {
case .givenCourse(let course):
try container.encode(course, forKey: .givenCourse)
case .professor(let professor):
try container.encode(professor, forKey: .professor)
case .unknown: break
}
}
}
MyDataContainer<DataClass>
You can simply include both Optional givenCourse and professor models in your DataClass. Each possible model will be decoded thanks to the Codable protocol.
Edit DataClass:
struct DataClass: Codable {
let givenCourse: GivenCourse?
let professor: Professor?
enum CodingKeys: String, CodingKey {
case givenCourse = "GivenCourse"
case professor = "Professor"
}
}
Use/Debug:
let decodedData = try! JSONDecoder().decode(Response.self, from: jsonData)
if decodedData.type == "Professor" {
print(decodedData.data.professor)
} else if decodedData.type == "Courses" {
print(decodedData.data.givenCourse)
}

Swift, How to Parse/Decode the JSON using Decodable and Codable, When key are unknow/dynamic

Below is my JSON, and I am not able to decode(using CodingKeys)
The data within the regions key is a Dictionary ("IN-WB", "IN-DL" & so on....), as the keys are dynamic, it can be changed more or less.
Please help me parsing the same using Decodable and Codable.
All the data should be within the single model.
{
"provider_code": "AIIN",
"name": "Jio India",
"regions": [
{
"IN-WB": "West Bengal"
},
{
"IN-DL": "Delhi NCR"
},
{
"IN-TN": "Tamil Nadu"
},
{
"IN": "India"
}
]
}
Just use a Dictionary for the regions.
struct Locations: Codable {
let providerCode: String
let name: String
let regions: [[String: String]]
enum CodingKeys: String, CodingKey {
case providerCode = "provider_code"
case name, regions
}
}
You cannot create a specific model for the regions as you wont know the property names
One of possible approach, without using dictionary. But still we have to found key at first )
I like this style as we can use Regions from beginning.
// example data.
let string = "{\"provider_code\":\"AIIN\",\"name\":\"Jio India\",\"regions\":[{\"IN-WB\":\"West Bengal\"},{\"IN-DL\":\"Delhi NCR\"},{\"IN-TN\":\"Tamil Nadu\"},{\"IN\":\"India\"}]}"
let data = string.data(using: .utf8)!
// little helper
struct DynamicGlobalKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
// model
struct Location: Decodable {
let providerCode: String
let name: String
let regions: [Region]
}
extension Location {
struct Region: Decodable {
let key: String
let name: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DynamicGlobalKey.self)
key = container.allKeys.first!.stringValue
name = try container.decode(String.self, forKey: container.allKeys.first!)
}
}
}
// example of decoding.
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let location = try decoder.decode(Location.self, from: data)

Why isn't my text being added to my label?

Revised post: So the code posted below is my stuct
struct AnimeJsonStuff: Decodable {
let data: [AnimeDataArray]
}
struct AnimeLinks: Codable {
var selfStr : String?
private enum CodingKeys : String, CodingKey {
case selfStr = "self"
}
}
struct AnimeAttributes: Codable {
var createdAt : String?
var slug : String?
private enum CodingKeys : String, CodingKey {
case createdAt = "createdAt"
case slug = "slug"
}
}
struct AnimeRelationships: Codable {
var links : AnimeRelationshipsLinks?
private enum CodingKeys : String, CodingKey {
case links = "links"
}
}
struct AnimeRelationshipsLinks: Codable {
var selfStr : String?
var related : String?
private enum CodingKeys : String, CodingKey {
case selfStr = "self"
case related = "related"
}
}
struct AnimeDataArray: Codable {
let id: String?
let type: String?
let links: AnimeLinks?
let attributes: AnimeAttributes?
let relationships: [String: AnimeRelationships]?
private enum CodingKeys: String, CodingKey {
case id = "id"
case type = "type"
case links = "links"
case attributes = "attributes"
case relationships = "relationships"
}
}
This code is my function for parsing data:
func jsonDecoding() {
let jsonUrlString = "https://kitsu.io/api/edge/anime"
guard let url = URL(string: jsonUrlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
do {
let animeJsonStuff = try JSONDecoder().decode(AnimeJsonStuff.self, from: data)
for anime in animeJsonStuff.data {
// print(anime.id)
// print(anime.type)
// print(anime.links?.selfStr)
let animeName = anime.attributes?.slug
print(animeName)
DispatchQueue.main.async {
self.nameLabel.text = animeName
}
for (key, value) in anime.relationships! {
// print(key)
// print(value.links?.selfStr)
// print(value.links?.related)
}
}
} catch let jsonErr {
print("Error serializing json", jsonErr)
}
}.resume()
}
This is what the console prints out:
Optional("cowboy-bebop")
Optional("cowboy-bebop-tengoku-no-tobira")
Optional("trigun")
Optional("witch-hunter-robin")
Optional("beet-the-vandel-buster")
Optional("eyeshield-21")
Optional("honey-and-clover")
Optional("hungry-heart-wild-striker")
Optional("initial-d-fourth-stage")
Optional("monster")
Optional("cowboy-bebop")
Optional("cowboy-bebop-tengoku-no-tobira")
Optional("trigun")
Optional("witch-hunter-robin")
Optional("beet-the-vandel-buster")
Optional("eyeshield-21")
Optional("honey-and-clover")
Optional("hungry-heart-wild-striker")
Optional("initial-d-fourth-stage")
Optional("monster")
Optional("cowboy-bebop")
Optional("cowboy-bebop-tengoku-no-tobira")
Optional("trigun")
Optional("witch-hunter-robin")
Optional("beet-the-vandel-buster")
Optional("eyeshield-21")
Optional("honey-and-clover")
Optional("hungry-heart-wild-striker")
Optional("initial-d-fourth-stage")
Optional("monster")
It now displays the text but it only displays the last optional called monster and not all the other ones when I have three cells. It only displays monster in each cell.
It should be
1st cell: Cowboy-bebpop
2nd cell: cowboy-bebop-tengoku-no-tobira
3rd cell: trigun
and etc
I can't see where do you set post variable.
Where you put nambeLabel into Controller's view hierarchy?
And maybe you should set nameLabel.text in main thread:
DispatchQueue.main.async {
self.nameLabel.attributedText = attributedText
}