Home About Contact

 

JSON parsing and examples of creating views in loop with SwiftUI

4 min read

Most of our apps need to deal with JSON parsing these days. Especially when your apps need to load data from REST API which provides data in the form of JSON.

With SwiftUI, the use of ForEach and List that allow the creation of views in a loop with ease, making it better to have the data struct to be parseable from JSON files instead of having to hard code them in an array; for better flexibility of change.

In Swift, to decode JSON into struct or object, what you need to do is to make the struct or class conforming to the Decodable protocol as folllows:

struct MenuItem : Decodable, Identifiable {    
    var id : Int    
    var name : String
    var imageName : String
}

The above struct MenuItem also conforms to Identifiable protocol as we need to use it with ForEach or List in our following examples.

For the following String of json data.

let jsonData ="""
[
   { "id": 1, "name" : "Apple TV", "imageName" : "appletv"},
   { "id": 2, "name" : "Apple Watch", "imageName" : "applewatch"},
   { "id": 3, "name" : "Car", "imageName" : "car"},
   { "id": 4, "name" : "iPhone", "imageName" : "iphone"}
]
"""

To decode it into an array of MenuItem, the code is just as below:

let menuItems = try! JSONDecoder().decode([MenuItem].self, from: jsonData)

The JSONDecoder has a property keyDecodingStrategy, which treats the keys as camel case by default. Or you can set it to convertFromSnakeCase, if your json data is provided with snake-case keys. The following example of JSON data has snake-case keys. (Snake case is where the imageName is written as image_name instead)

let jsonData ="""
[
   { "id": 1, "name" : "Apple TV", "image_name" : "appletv"},
   { "id": 2, "name" : "Apple Watch", "image_name" : "applewatch"},
   { "id": 3, "name" : "iPhone", "image_name" : "iphone"}
]
"""

To decode the above we set keyDecodingStrategy to convertFromSnakeCase :

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let menuItems = try! JSONDecoder().decode([MenuItem].self, from: jsonData)

If the json data comes with a mixture of different key namings:

[
  {"tshirt_color_in_hex":"#0000A0","qty":12,"name":"Dark Blue"},
  {"tshirt_color_in_hex":"#a52a2a","qty":10,"name":"Brown"},
  {"tshirt_color_in_hex":"#FF0000","qty":5,"name":"Red"},
  {"tshirt_color_in_hex":"#00FF00","qty":3,"name":"Lime"},
]

Then we need to add our own mapping by defining a custom CodingKeys enum in our struct:

struct TShirt : Decodable {
  var hexColor : String
  var name : String
  var quantity : Int
  enum CodingKeys: String, CodingKey {
     case name
     // Map the JSON key "tshirt_color_in_hex" to the Swift property name "hexColor"
     case hexColor = "tshirt_color_in_hex"
     // Map the JSON key "qty" to the Swift property name "quantity"
     case quantity = "qty"
  }
}

Some SwiftUI examples:

Here is an extension of String which provides a generic function that decodes itself into the type specified by the placeholder type T.

It should come in handy when you want to decode json data stored in a constant or variable of String; into any type conforming to Decodable.

Please note that the function will force crash your app with fatalError, when any exception raised due to invalid json format.

extension String {
  func decodeJson <T: Decodable> (_ type : T.Type ,
  dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate,
  keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T {
        
     let jsonData = self.data(using: .utf8)!
       
      do {    
         let decoder = JSONDecoder()
         decoder.dateDecodingStrategy = dateDecodingStrategy
         decoder.keyDecodingStrategy = keyDecodingStrategy
   
         let result = try decoder.decode(type, from: jsonData)   
         return result
      }
      catch {      
         fatalError("err:\(error)")
      }  
   }
}

You can use it as the example below to decode the String constant into any array of MenuItem :

let menuItems =
   """
   [
      { "id": 1, "name" : "Apple TV", "imageName" : "appletv"},
      { "id": 2, "name" : "Apple Watch", "imageName" : "applewatch"},
      { "id": 3, "name" : "Car", "imageName" : "car"},
      { "id": 4, "name" : "iPhone", "imageName" : "iphone"},
   ]
   """.decodeJson([MenuItem].self)

And the decoded array of MenuItem is used to create views in loop with the ForEach in SwiftUI as follows:

VStack (alignment: .leading,  spacing: 20){
   ForEach(menuItems) { item in
      HStack(spacing: 20) {
        Image(systemName: item.imageName)
        .frame(width: 30, height: 30)
        Text (item.name)         
      }
   }
}

The result is shown below :

Please note that the second parameter in the String extension function decodeJson is the dateDecodingStrategy, which is set to .deferredToDate by default – the Apple’s own date format, and it tracks the number of seconds and milliseconds since January 1st 2001. It’s not useful when outside of Apple’s platform.

If you want to decode a custom format into Date type, the following extension functions of DateFormatter will be useful.

extension DateFormatter {
  func jsonDateDecodingStrategy(dateFormat : String )
   -> JSONDecoder.DateDecodingStrategy{
     self.dateFormat = dateFormat
     return .formatted(self)
  }
    
  func string( from : Date ,dateFormat : String ) -> String {
      self.dateFormat = dateFormat
      return self.string(from: from)        
  }  
}

The first function is to provide a DateDecodingStrategy according to the format you set. The second function will be useful to convert any Date type to String with the specified format. An example of using it in SwiftUI is as follows:

A struct of NewsItem:

struct NewsItem : Decodable, Identifiable {
  var id : Int
  var title : String
  var datePublished : Date
}

Decode a constant of String with the datePublished in specific format “yyyy-MM-dd HH:mm:ss” into array of NewsItem:

private let newsItems  = """
[
   {"id":1,"title":"We need to bring community and sustainability back to the heart of Ethereum",
   "datePublished":"2020-10-10 16:18:29"},
   {"id":2,"title":"Cryptocurrency's Rocky Road: China's ICO Ban",
   "datePublished":"2020-10-10 17:28:12"},
   {"id":3,"title":"Bitcoin surges to new peak as it continues bull run to next milestone: $30,000",
    "datePublished":"2020-10-10 18:05:34"}
]
""".decodeJson([NewsItem].self,
    dateDecodingStrategy:
    DateFormatter()
   .jsonDateDecodingStrategy(dateFormat: "yyyy-MM-dd HH:mm:ss"))

Display the array of NewsItem in a List and convert the Date to another custom format “dd/MMM/yy HH:mm:ss” for display.

List(newsItems){ newsItem in
  VStack(alignment: .leading, spacing:10){
    Text(newsItem.title)
    .font(.headline)
                
    Text(DateFormatter().string(from: 
    newsItem.datePublished,
    dateFormat: "dd/MMM/yy HH:mm:ss"))
    .font(.caption)
  }
}

The result is as below:

Spread the love
Posted on January 2, 2021 By Christopher Chee

2 thoughts on “JSON parsing and examples of creating views in loop with SwiftUI”

Please leave us your comments below, if you find any errors or mistakes with this post. Or you have better idea to suggest for better result etc.


Our FB Twitter Our IG Copyright © 2024