Swift - Using Decodable to decode JSON array of just strings - json

I have a sample JSON where its just an array of strings and has no keys and would like to use the Decodable protocol to consume the JSON and create a simple model out of it.
The json looks like this:
{ "names": [ "Bob", "Alice", "Sarah"] }
Just a collection of strings in an simple array.
What I'm unsure about is how do I use the new Swift Decodable protocol to read this into a model without a key.
Most of the examples I've seen assume the JSON has a key.
IE:
// code from: Medium article: https://medium.com/#nimjea/json-parsing-in-swift-2498099b78f
struct User: Codable{
var userId: Int
var id: Int
var title: String
var completed: Bool
}
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode([User].self, from:
dataResponse) //Decode JSON Response Data
print(model)
} catch let parsingError {
print("Error", parsingError)
}
This above example assumes that the json is a key-value; how can I use the decodable protocol to de-code the JSON without keys?
With thanks

The corresponding struct of this JSON is
struct User: Decodable {
let names: [String]
}
and decode
let model = try decoder.decode(User.self, from: dataResponse)
and get the names with
let names = model.names
or traditionally without the overhead of JSONDecoder
let model = try JSONSerialization.jsonObject(with: dataResponse) as? [String:[String]]

For this simple structure of json , i guess it's better not to create any structs and use
let model = try decoder.decode([String:[String]].self, from: dataResponse)
print(model["names"])
the json fiting for your model is
{
"names": [{
"userId": 2,
"id": 23,
"title": "gdgg",
"completed": true
}]
}
struct Root: Codable {
let names: [User]
}
struct User: Codable {
let userId, id: Int
let title: String
let completed: Bool
}

Related

How to decode dictionary JSON response in Swift?

struct Chat: Codable, Identifiable {
let id = UUID()
var Messages: [Messages]
}
class ChatApi : ObservableObject{
#Published var chats = Chat()
func loadData(completion:#escaping (Chat) -> ()) {
let urlString = prefixUrl+"/room"
let url = NSURL(string: urlString as String)!
var request = URLRequest(url: url as URL)
request.setValue(accessKey, forHTTPHeaderField: "X-Access-Key-Id")
request.setValue(secretkey, forHTTPHeaderField: "X-Access-Key-Secret")
URLSession.shared.dataTask(with: request) { data, response, error in
let chats = try! JSONDecoder().decode(Chat.self, from: data!)
print(chats)
DispatchQueue.main.async {
completion(chats)
}
}.resume()
}
}
I'm not able to decode the following JSON response using Swift.
{
"Messages": [
{...}
]
}
I have tried the above ways and Xcode keeps throwing error. Although I'm able to decode JSON response with another function that are like this
[
{...},
{...},
{...}
]
I'm able to decode JSON response that are returned as arrays but not as dictionaries.
Example response to decode
{
"Messages": [
{
"_id": "MS4mMbTXok8g",
"_created_at": "2022-04-05T10:58:54Z",
"_created_by": {
"_id": "Us123",
"Name": "John Doe",
},
"_modified_at": "2022-04-05T10:58:54Z",
"Type": "Message",
"_raw_content": "ss",
"RoomId": "Ro1234",
},
{
"_id": "MS4m3oYXadUV",
"_created_at": "2022-04-04T15:22:21Z",
"_created_by": {
"_id": "Us678",
"Name": "Jim Lane",
},
"_modified_at": "2022-04-04T15:22:21Z",
"Type": "Message",
"_raw_content": "ss",
"RoomId": "Ro1234",
}
]
}
The data model that I've used is
struct CreatedBy: Codable {
var _id: String
var Name: String
}
struct Messages: Codable {
var _id: String
var _created_by: CreatedBy?
var `Type`: String?
var _raw_content: String
}
struct Chat: Codable, Identifiable {
let id = UUID()
var Messages: [Messages]
}
The error message before compilation is Editor placeholder in source file
I am going to introduce you to a couple of sites that will help when handling JSON decoding: JSON Formatter & Validator and Quicktype. The first makes sure that the JSON that you are working off of is actually valid, and will format it into a human readable form. The second will write that actual decodable structs. These are not perfect, and you may want to edit them, but they will get the job done while you are learning.
As soon as you posted your data model, I could see the problem. One of your variables is:
var `Type`: String?
The compiler is seeing the ` and thinking it is supposed to be a placeholder. You can't use them in the code.
Also, though there is not any code posted, I am not sure you need to make Chat Identifiable, as opposed to Messages which could be, but are not. I would switch, or at least add, Identifiable to Messages. I also made CreatedBy Identifiable since it also has a unique id.
The other thing you are missing which will make your code more readable is a CodingKeys enum. This translates the keys from what you are getting in JSON to what you want your variables to actually be. I ran your JSON through the above sites, and this is what came up with:
// MARK: - Chat
struct Chat: Codable {
let messages: [Message]
enum CodingKeys: String, CodingKey {
case messages = "Messages"
}
}
// MARK: - Message
struct Message: Codable, Identifiable {
let id: String
let createdAt: Date
let createdBy: CreatedBy
let modifiedAt: Date
let type, rawContent, roomID: String
enum CodingKeys: String, CodingKey {
case id = "_id"
case createdAt = "_created_at"
case createdBy = "_created_by"
case modifiedAt = "_modified_at"
case type = "Type"
case rawContent = "_raw_content"
case roomID = "RoomId"
}
}
// MARK: - CreatedBy
struct CreatedBy: Codable, Identifiable {
let id, name: String
enum CodingKeys: String, CodingKey {
case id = "_id"
case name = "Name"
}
}
This gives you conventional Swift variables, as opposed to the snake case the JSON is giving you. Try this in your code and let us know if you have any more problems.

Decode a nested object in Swift 5 with custom initializer

I have an API which returns a payload like this (just one item is included in the example).
{
"length": 1,
"maxPageLimit": 2500,
"totalRecords": 1,
"data": [
{
"date": "2021-05-28",
"peopleCount": 412
}
]
}
I know I can actually create a struct like
struct Root: Decodable {
let data: [DailyCount]
}
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
For different calls, the same API returns the same format for the root, but the data is then different. Moreover, I do not need the root info (length, totalRecords, maxPageLimit).
So, I am considering to create a custom init in struct DailyCount so that I can use it in my URL session
let reports = try! JSONDecoder().decode([DailyCount].self, from: data!)
Using Swift 5 I tried this:
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
extension DailyCount {
enum CodingKeys: String, CodingKey {
case data
enum DailyCountCodingKeys: String, CodingKey {
case date
case peopleCount
}
}
init(from decoder: Decoder) throws {
// This should let me access the `data` container
let container = try decoder.container(keyedBy: CodingKeys.self
peopleCount = try container.decode(Int.self, forKey: . peopleCount)
date = try container.decode(String.self, forKey: .date)
}
}
Unfortunately, it does not work. I get two problems:
The struct seems not to conform anymore to the Decodable protocol
The CodingKeys does not contain the peopleCount (therefore returns an error)
This can’t work for multiple reasons. You are trying to decode an array, so your custom decoding implementation from DailyCount won’t be called at all (if it were to compile) since at the top level your JSON contains an object, not an array.
But there is a much simpler solution which doesn’t even require implementing Decodable yourself.
You can create a generic wrapper struct for your outer object and use that with whatever payload type you need:
struct Wrapper<Payload: Decodable>: Decodable {
var data: Payload
}
You then can use this to decode your array of DailyCount structs:
let reports = try JSONDecoder().decode(Wrapper<[DailyCount]>.self, from: data).data
This can be made even more transparent by creating an extension on JSONDecoder:
extension JSONDecoder {
func decode<T: Decodable>(payload: T.Type, from data: Data) throws -> T {
try decode(Wrapper<T>.self, from: data).data
}
}
Sven's answer is pure and elegant, but I would be remiss if I didn't point out that there is also a stupid but easy way: dumpster-dive into the "data" without using Codable at all. Example:
// preconditions
let json = """
{
"length": 1,
"maxPageLimit": 2500,
"totalRecords": 1,
"data": [
{
"date": "2021-05-28",
"peopleCount": 412
}
]
}
"""
let jsonData = json.data(using: .utf8)!
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
// okay, here we go
do {
let dict = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [AnyHashable:Any]
let arr = dict?["data"] as? Array<Any>
let json2 = try JSONSerialization.data(withJSONObject: arr as Any, options: [])
let output = try JSONDecoder().decode([DailyCount].self, from: json2)
print(output) // yep, it's an Array of DailyCount
} catch {
print(error)
}

Converting API JSON data to a Swift struct

I am using Swift for the first time and I'd like to be able to process some info from an API response into a usable Swift object.
I have (for example) the following data coming back from my API:
{
data: [{
id: 1,
name: "Fred",
info: {
faveColor: "red",
faveShow: "Game of Thrones",
faveIceCream: "Chocolate",
faveSport: "Hockey",
},
age: "28",
location: "The Moon",
},{
...
}]
}
In swift I have the data coming back from the API. I get the first object and I'm converting it and accessing it like so:
let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
let dataParentNode = json["data"] as! [[String:Any]]
let firstObject = dataParentNode[0]
let _id = firstObject["id"] as? String ?? "0"
let _name = firstObject["name"] as? String ?? "Unknown"
This is fine until I want to start processing the sub-objects belonging to the first object so I came up with the following structs to try and make this cleaner.
Please note - I don't need to process all of the JSON data coming back so I want to convert it to what I need in the structs
struct PersonInfo : Codable {
let faveColor: String?
let faveShow: String?
}
struct Person : Codable {
let id: String?
let name: String?
let info: PersonInfo?
}
When I take this:
let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
let dataParentNode = json["data"] as! [[String:Any]]
let firstObject = dataParentNode[0]
and then try to convert firstObject to Person or firstObject["info"] to PersonInfo I can't seem to get it to work (I get nil).
let personInfo = firstObject["info"] as? PersonInfo
Can anyone advise please? I just need to get my head around taking API response data and mapping it to a given struct (with sub-objects) ignoring the keys I don't need.
You can simply use decode(_:from:) function of JSONDecoder for this:
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode([String: [Person]].self, from: data)
let firstObject = decoded["data"]?.first
} catch {
print(error)
}
Even better you can add another struct to you model like this:
struct PersonsData: Codable {
let data: [Person]
}
And map your JSON using that type:
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(PersonsData.self, from: data)
let firstObject = decoded.data.first
} catch {
print(error)
}
Update: Your Person struct might need a little change, because the id property is integer in your JSON.
So, it will end up like this:
struct Person : Codable {
let id: Int?
let name: String?
let info: PersonInfo?
}

How to access varying JSON key in Swift 4.2 natively?

I have the following dummy JSON data for a bus to school.
{
"toSchool": {
"weekday": [{
"hour": 7,
"min": 10,
"type": null,
"rotary": false
}],
"sat": [{
"hour": 8,
"min": 15,
"type": null,
"rotary": true
}]
}
}
I would like to access "weekday" and "sat" key with a variable based on user input. How can I achieve this natively?
Using SwiftyJSON, it is fairly simple like below
let json = try JSON(data: data)
let userDirection = "shosfc"
let userWeek = "weekday"
let busList = json[userDirection][0][userWeek]
However, I was wondering how this would be done natively to remove dependencies.
It seems that CodingKey and enum might be the way to handle this. When the example is as simple as this, I can understand. However, I just cannot get my head around it for my particular usage where it involves custom objects not just String.
How can I do this? Please help.
This is based on your earlier question
func bus(isWeekday: Bool = true) -> [Bus] {
return isWeekday ? shosfc.weekDay : shosfc.sat
}
I think code that below will work:
struct SampleResponse: Codable {
let toSchool: ToSchool
}
struct ToSchool: Codable {
let weekday, sat: [Sat]
}
struct Sat: Codable {
let hour, min: Int
let type: String?
let rotary: Bool
}
To decode this type of response, you must decode this JSON with SampleResponse type.
let sampleResponse = try? newJSONDecoder().decode(SampleResponse.self, from: jsonData)
After that, you can reach variables like you asked.
You can convert JSON string to Dictionary in swift and access it the same way you just did:
func parseToDictionary(_ jsonStr: String) -> [String: Any]? {
if let data = jsonStr.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}
let jsonStr = "{Your JSON String}"
let json = parseToDictionary(jsonStr)
let userDirection = "shosfc"
let userWeek = "weekday"
let busList = json[userDirection][0][userWeek]

Using Swift 4 Decodable to parse JSON with mixed property values

I am trying to update my app to use Swift 4 Decodable - and am pulling down data from a JSON api which has child values which could be:
A Child Object
A String
An Int
Here is the Json Api response:
var jsonoutput =
"""
{
"id": "124549",
"key": "TEST-32",
"fields": {
"lastViewed": "2018-02-17T21:40:38.864+0000",
"timeestimate": 26640
}
}
""".data(using: .utf8)
I have tried to parse it using the following: which works if I just reference the id and key properties which are both strings.
struct SupportDeskResponse: Decodable{
var id: String
var key: String
//var fields: [String: Any] //this is commented out as this approach doesn't work - just generated a decodable protocol error.
}
var myStruct: Any!
do {
myStruct = try JSONDecoder().decode(SupportDeskResponse.self, from: jsonoutput!)
} catch (let error) {
print(error)
}
print(myStruct)
How do I decode the fields object into my Struct?
You should create a new Struct adopting Decodable protocol like this :
struct FieldsResponse: Decodable {
var lastViewed: String
var timeestimate: Int
}
Then you can add it to your SupportDeskResponse
var fields: FieldsResponse