WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: How to decode a JSON dictionary?

Forums > Swift

Good morning (o;

I've seen many examples where JSONs are fetched and decoded....but all examples I've found are for JSON arrays... But how would I define a Struct and decode a simple JSON dictionary like:

{
        "expected": 15017,
        "month": 7508.70,
        "today": 320.00,
        "week": 2717.50
}

thanks in advance richard

   

let decoderedJSON = """
{
    "expected": 15017,
    "month": 7508.70,
    "today": 320.00,
    "week": 2717.50
}
""".data(using: .utf8)!

struct DecoderedThing: Decodable {
    var expected: Int
    var month: Double
    var today: Double
    var week: Double
}

do {
    let thing: DecoderedThing = try JSONDecoder().decode(DecoderedThing.self, from: decoderedJSON)
    print(thing)
} catch {
    print(error)
}

   

Apple Developer Decoder Documentation

Except yours will be for the Int and Double types.

struct myStruct: Codable {
    var expected: Int
    var month: Double
    var today: Double
    var week: Double
}

For more general and advances cases of encodable and decodable Apple Developer Codable Documentation

   

Hacking with Swift is sponsored by Emerge

SPONSORED Why are Swift reference types bad for app startup time, and what’s the performance cost of protocol conformances? That’s just a couple of the topics you can learn about on the Emerge blog — written by the app performance experts behind Emerge’s advanced app optimization and monitoring tools, based on their experience of working at companies like Apple, Airbnb, Snap, and Spotify.

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

If you use Ducky Model Editor it will help on any JSON format.

How to use @StewartLynch video Ducky - Model Editor

2      

Just bought Ducky (o;

I should check my profile here more often as the forum doesn't send any emails when a reply has been posted...

   

Hmm..still bugs me this correct initialization..

Ducky converted the JSON fine and spit out:

struct Series: Codable {
  struct Data: Codable, Identifiable {
    var id: Int { return Int(dTRowID) ?? 0 }
    let dTRowID: String
    let iD: String
    let name: String
    let label: String
    let value: String

    private enum CodingKeys: String, CodingKey {
      case dTRowID = "DT_RowId"
      case iD = "ID"
      case name = "Name"
      case label
      case value
    }
  }

  let data: [Data]
}

And works fine with Swift Playground with:

let thing: Series = try JSONDecoder().decode(Series.self, from: decoderedJSON)

Still have no clue and haven't found an answer how to initialize the Series dictionary correctly.... this just throws an erry that JSON decoder wants to decode an array:

@Published var series = [Series]()

...
let series = try! JSONDecoder().decode([Series].self, from: data)

So "series" is initialized as array and not as a dictionary....and all search results about creating an empty dictionary didn't help at all...

   

Yes, your declaration of series is an array of Series, so calling the decoder is trying to decode an array Series items.

You may want to check out the dictionary tutorial / help here or here.

To declare an empty dictionary, you will need to pass the types to the dictionary and immediatley create it.

var aDictionary = [String: String]()

or

var myDictionary = [String: Int]()
var myTypes = [String: <T>]()      // note <T> is placeholder notation for a generic type

In terms of your simple JSON

{
        "expected": 15017,
        "month": 7508.70,
        "today": 320.00,
        "week": 2717.50
}

what roosterBoy wrote, some time ago, is how you would decode that JSON.

   

Hmm...actually the simple dictionary snippet works fine....just not a dictionary holding an array:

{
  "data": [
    {
      "ID": "0",
      "DT_RowId": "0",
      "Name": "- none -",
      "label": "- none -",
      "value": "0"
    },
    ......
  ]
}
class FetchSeries: ObservableObject {
    @Published var series = [String: <T>]()

gives:

'<' is not a prefix unary operator"

Either the struct generated from Ducky is not usable here or I have to rewrite the PHP code so it returns just an array instead of an array in a dictionary. But then again that would mean I coudn't use the same JSON API for Swift and for Datatables web applications any longer...or leading to more API calls.

Seen that others define separate struct per JSON element like:

struct WeatherModel: Codable {
    let data: WeatherData
}

struct WeatherData: Codable {
    let timelines: [WeatherTimelines]
}

struct WeatherTimelines: Codable {
    let intervals: [WeatherIntervals]
}

But all examples I've found don't use it in a class with ObservableObject where the dictionary has to be initialized.

The same struct generated with Ducky works fine with:

let thing: Series = try JSONDecoder().decode(Series.self, from: decoderedJSON)

Just not with my class: (here the whole snippet)

struct Series: Codable {
  struct Data: Codable, Identifiable {
    var id: Int { return Int(dTRowID) ?? 0 }
    let dTRowID: String
    let iD: String
    let name: String
    let label: String
    let value: String

    private enum CodingKeys: String, CodingKey {
      case dTRowID = "DT_RowId"
      case iD = "ID"
      case name = "Name"
      case label
      case value
    }
  }

  let data: [Data]
}

class FetchSeries: ObservableObject {
    @Published var series: Series = [String: <T>]()

    func reload() {
        let url = URL(string: "https://myjsonapibackendserver.ch/ajax/json.series.php")!
        print(series)

        let urlSession = URLSession.shared
        var request = URLRequest(url: url)
        request.setValue(kToken, forHTTPHeaderField: kHeader)

        let task = urlSession.dataTask(with: request) { (data, response, error) in
            guard let data = data else {
                debugPrint("Error loading \(url): \(String(describing: error))")
                return
            }
            print(data)
            let series = try! JSONDecoder().decode(Series.self, from: data)
            OperationQueue.main.addOperation {
                self.series = series
            }
        }
        task.resume()
    }
}

Maybe the Series struct needs an intializer function for this to work?

   

Yesssss...that was exactly it (o;

Struct looks like this now (thanks to Ducky's initialize option ;o):

struct Series: Codable {
  struct Data: Codable, Identifiable {
    var id: Int { return Int(dTRowID) ?? 0 }
    let dTRowID: String
    let iD: String
    let name: String
    let label: String
    let value: String

    private enum CodingKeys: String, CodingKey {
      case dTRowID = "DT_RowId"
      case iD = "ID"
      case name = "Name"
      case label
      case value
    }
      init(iD: String, dTRowID: String, name: String, label: String, value: String) {
        self.iD = iD
        self.dTRowID = dTRowID
        self.name = name
        self.label = label
        self.value = value
      }
    }

    let data: [Data]

    init(data: [Data]) {
      self.data = data
    }
}

And then in my class initialize the dictionary like:

class FetchSeries: ObservableObject {
    @Published var series: Series = Series.init(data: [])

Wonder how complicated it would be with a more complex, nested dictionary (o;

   

You would write your own macOS helper app.

I did, as I found that the available apps were not able to handle more complicated structures. I needed nested arrays of various depths with some elements and sub-arrays being optonal. So my helper app processes TSV files exported from a spreadsheet. As this is a helper app I didn't spend much time developing it.

   

Well still a couple of those 100 days remaining for me ;-)

But I was surprised how fun development with Swift(UI) is.....and this pops up many ideas to implement on macOS/iOS.... after 2 days I was able to deploy a simple iOS app for my girlfriend to check on her iPhone online orders from her online shop when abroad....

I'm glad I haven't bought Xojo few weeks ago....so I'm glad I spent that money on HWS+ today (o;

Maybe a good point to list some available macOS helper apps here that people are using for development.

   

Hacking with Swift is sponsored by Emerge

SPONSORED Optimize your app’s startup time, binary size, and overall performance using Emerge’s advanced app optimization and monitoring tools. Reliably measure app size, speed up your app's startup time with Emerge's Launch Booster, and much more. Emerge is actively used by many of the top mobile development teams in the world.

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.