UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Struggling to understand JSON

Forums > 100 Days of SwiftUI

@CJ  

Hi

I'm struggling to understand JSON and wondering if anyone could either explain or point me somewhere to gain a better understanding? The way it is being introduced along with multi page apps, classes, protocols and different property observers, as a total beginner, I feel overwhelmed. I am just moving onto day 41 of the Moonshot app but have backtracked as I felt I was just getting lost in the fog. I get that it is used to store user settings and also to format files, but how it is doing it, and whether files need to be written in this way (or even how they are written in this way) I don't really get.

As a way of explaining the depth of my confusion, I'm going use the example below that Paul uses (I think on day 37). I have added the print functions myself. He uses it to explain JSON but honestly I am not sure what I am supposed to take away from this. The data part prints '41 bytes' and the print(user) basically, and predictably, shows the variable 'User.' That's fine but has this data been saved somewhere? Has it been encoded? What even does it mean to encode the data? Also what is the 'forKey' "UserData" used for? Is the data stored in a dictionary? How do I retrieve it? I just can't really visualise what is going on even at this point so when he starts trying to incorporate it into apps with several pages, I just can't keep track of it. The basic understanding just isn't there.

struct User: Codable {
    let firstName: String
    let lastName: String 
}

struct ContentView: View {
    @State private var user = User(firstName: "Taylor", lastName: "Swift")

    var body: some View {
        Button("save user") {
            let encoder = JSONEncoder()
            if let data = try? encoder.encode(user) {
                UserDefaults.standard.set(data, forKey: "UserData")
                print(data)
                print(encoder)
                print(user)

            }
        }
    }

2      

you ask a very good question , you see we need to accpet data and also share data, so to come to any kind of agreement on how to do it universally is a bit difficult , so this json is a part of that agreement, so what happens is that we now say that we can transmit and accept data using this format, but what that actually means? So what happens is there might me a server some where and that server holds some info and say there is a browser , that wants to read that information ...

so now how do we send a data that can be universally understood by browsers and then also in human readable format, that format is json, which is also stateless, means it does not remember its previous state ...

this is what really json is you must see files like

{
  "array": [
    1,
    2,
    3
  ],
  "boolean": true,
  "color": "gold",
  "null": null,
  "number": 123,
  "object": {
    "a": "b",
    "c": "d"
  },
  "string": "Hello World"
}

now you can see, that there is a pattern, there is this "array", "boolean" etc and then some values, in front of them, true/false etc, this is one of the requirements in some languages like swift that it has to represent either an array or dictioary , ie, key/value pairing...

but to boil it down, its an agreed format these days where we have to create a json file in a particular way and then the language will have feature to read it and use. the information ... your above example is just a way the swift language is trying to read and use that info from json file

do not rush , these things took years to formualte , no one not even a maths phd will understand them fast ..

good luck

2      

@CJ  

Many thanks for your response @AmitShrivastava. To clarify, what I am really looking for is an understanding of how JSON works in a practical sense within the program. What are the processes that happen when the button is pressed? What does it mean to 'encode' the data, and why does it need to be encoded/decoded? Is it saved somewhere, if so where and how do I access it? of course in more general terms, beyond the example I am also unclear exactly what a JSON file is, how it is created and whether you could say, ever use an ordinary text file in a program? I get that the format is made to be read in such a way that the program recognises a date as a date for instance (rather than a simple string) but I don't have any broader understanding of how you might use data within apps to know where JSON would and wouldn't be used.

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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

this seems like precise article with some topics you want to understand - https://www.avanderlee.com/swift/json-parsing-decoding/

2      

@CJ  

Thanks for the link. Unfortunately, it doesn't really help as I don't understand what he is trying to decode or why he is trying to decode it. I am several steps before this point. All I can see is that he has started off with some white text, written quite a complicated struct and ended up with the same text but in red. For the seasoned developer, I'm sure there is more to it but it means nothing to me at this time.

My questions, I think are more fundamental, why is he using JSON data in the first place, how has he placed it into his program and what happens to it when someone accesses the site? Although, to be honest, that's even getting a bit complicated, which is why I used the example I did because really if someone could explain the process in terms of a button being pressed and a process being activated in very straightforward terms I would be even happier.

2      

@cj asks:

I am also unclear exactly what a JSON file is, how it is created

Like Amit, I agree this is a great question. And am pleased that you're not afraid to ask it in this forum. Many programmers here are greatly familiar with JSON and all the concepts you're working to understand. Not understanding JSON is a huge barrier to moving forward. So, let's clear some things up with a few real world examples and help push you up.

Exchanging Data

If I asked you to send me a list of what you ate for breakfast, you might reply:

"Pop-tart, bacon (3 pieces), coffee"

Amit might respond with:

  • Bowl of Oatmeal
  • Orange slices
  • 8 ounces of whole milk

My answer might be:

"(a) Bagel, (b) cream cheese, (c) grapes, (d) Earl Grey (hot!)"

The three of us instantly understand these formats. But please realize that none of us used the same format. Now, extend this data exchange problem to each student in your French course, your immediate family, your mates in Hufflepuff, and the 20+ people who rode your bus this morning. Your breakfast-data-exchange problem became torture!

Data Exchange Format

So first and foremost, think of JSON as a data exchange format. I don't care how Amit stores their daily breakfast diary. I don't care how you record your daily chow, either. All I ask is that when you and Amit send it, you use this common format, which I just this moment fabricated.

// This is a JSON file.
{
        "date": "2022-12-31", // Notice for every *key*, there is a *value*
        "name": "Obelix",
        "meal": "breakfast",
        "items": [
            "coffee",
            "bagel",
            "cream cheese",
            "grapes"
        ]
}

Now, if you send me your breakfast every day in this format, I'll know who, which day, which meal, and the items and drinks in the meal. Using this format, I can write a process to read JSON files from any aquaintence who send me their daily food diaries.

As you can see, JSON files are just text. Because "the internet" uses JSON to exchange data between services, this format is very fast, and well supported. Exchange data between services: Think Paypal payments to Amazon. Think map data from google maps to Zillow. Think UPS shipping prices to LLBeans. Think news articles from [FAVORITE NEWS SOURCE] to your browser. JSON data flies across the internet.

2      

JSON: Part II

Nice! You just sent me a JSON file listing your breakfast. It looks like this:

{
     "meal": "breakfast",  // <- Out of order?
     "date": "2022-12-31",
     "name": "cj",
     "dine_in": true,   // <- A boolean.
     "items": [
         "pop-tart",
         "bacon",
         "coffee"
     ]
 }

More Questions

Let's answer your next question:

I don't understand what he is trying to decode or why he is trying to decode it.

I received your meal file as a TEXT file in JSON format. But my application doesn't have business objects (Structs, Classes) for a chunk o' JSON. Before I can use this JSON data in my application, I have to map it, element-by-element, to something that I DO HAVE in my application. If you're following along with the 100 Days of SwiftUI program, then you know I'm talkin' about Structs.

Indeed. In my application I have a Meal struct that looks something like this:

struct Meal: Identifiable {
    let id =        UUID()     // Unique it.
    var name:       String     // probably breakfast, 2nd breakfast, lunch, tea, dinner
    var dateLogged: Date
    var forPerson:  String     // CJ, Obelix, Amit, etc.
    var items =    [String]()  // list the breakfast items, coffee, toast, kippers, etc
}

Decoding

Nice! Now I have the task of taking this nicely formed JSON file (text) and transmogrifying it into a Meal structure in Swift. How do you get the parts of the JSON to match up with the parts of the Meal struct? To make this tougher, in the JSON a person is referred to as name, but in the struct they are referred to as forPerson. Also note, in the struct the variable name is for the meal name, not the person's name.

Also notice that meal is in the first position, whilst in my example it's in the third position. I also notice that you provided a boolean indicating you ate at home, rather than at the bus stop.

Nightmare!

The process of mapping the JSON text pieces to their corresponding parts in a Meal struct is called: Decoding. The lessons that @twoStraws provides gives you the basic guidance you need to map JSON data into Swift structs.

JSON offers many options, and you'll increase your knowledge via other videos and online articles. But it's worth noting here, that JSON doesn't care about the order of the key:value pairs. And when my application decodes the JSON you sent, it can freely ignore the extra data that you sent along for the ride across the internet.

Encoding

The opposite is called Encoding. Assume you have a few dozen meals in your popular diary appliction all stored as Structs. You want to send these to Amit, what are your steps? Well, you don't know (or need to know) how Amit stores meal data in their application. All you need to do is get your Meal structs into JSON format, then send this to Amit's application. In short, you take the meal data from your Meal structs and encode them into JSON.

2      

@CJ  

Many thanks for your response @Obelix. The idea of a data format that is universally readable and is then reformatted for different users makes sense. I had got confused by the terms encoding and decoding as if somehow something was being scrambled but it seems clear now that the encoding simply means - 'write' to JSON format and decode, 'read' from JSON format, albeit with some translation. So the button above is presumably writing the data to JSON format? I think I understand but, I am still a bit confused about where that data has been saved and how to access it. There is a 'key' given "UserData" but I am confused as to how I would use this? I know at some point we learnt about keys with dictionaries but it seems like some time ago and even looking back at it I can't quite put it togther in my mind.

2      

UserDefaults.standard.set(data, forKey: "UserData")

This line really has nothing to do with understanding what JSON is. It's just storing the data that was encoded to JSON in the previous line into the UserDefaults system. You could do the same with an Int or a String or a Bool; it's not something specific to JSON.

2      

JSON Lesson: Part III

Now that you have a little more clarity on JSON and its primary purpose, it might be a good time for you to review @twoStraw's article from 2018.

See-> Codeable Cheat Sheet

Codable

So why might you ask your application's Structs to conform to the Codable protocol?

The short answer is:

You want to wrap your Struct in a nice, consise package that's

  • easy to send over the internet
  • in a format (JSON)
  • that other applications can read!

When you declare that your Struct conforms to the Codable protocol, you're declaring that it can be transformed from its Swift Struct format into a concise JSON format.*

  • footnote: Codable means your object can be both encoded AND decoded. You don't have to implement both. If it suits your solution, you may only need to conform to Encodable if you'll be sending data, and not receiving it. This discussion is outside the scope of this wee answer.

Is it always possible to encode and decode your Structs?

JSON is built on some fundamental data types:

  • a string
  • a number
  • an object (JSON object)
  • an array
  • a boolean
  • null

If you design a Struct such that it only has these data types, then your Struct can easily be morphed into a JSON object. But sometimes you design a Struct with other data types that do not conform to JSON's basic data types.

Extra Work

In these cases, you cannot simply turn your Struct into a JSON object. For example, your application's Struct may have a var that is a date range. Example:

    [...snip....]
    var dateRange = Date.now...Date.now.addingTimeInterval(10000) // some Range in your Struct
    [...snip...

How does Swift's encoder turn this dateRange var into a JSON object?

This will be part of your design. If you want to encode the dateRange var perhaps you'll implement two String computed properties (startDateRange, endDateRange) and ask the JSON encoder to use these strings instead.

When you retreive the JSON, you'll do the opposite. You'll decode these two JSON parts into strings, then use a Struct function to form a Date range that you can use in your application.

What is the best strategy?

What is the best strategy for encoding or decoding Swift data types that don't conform to JSON's built in types?

Hey! You're the programmer. This is your job to figure out. This is why you get paid the 'big bucks'.

Good luck!

Keep Coding!

2      

@CJ  

Many thanks @Obelix and @Roosterboy for your further explanations. It has helped clarify things a lot. @Roosterboy your comment has actually made me realise that I was, in additon to being confused about JSON, also confused by the use of User Defaults and I had linked them together in my head. So, if you don't mind, I just have a couple more questions based on this, using iExpense as an example.

My understanding so far is that the struct below creates data which conforms to the codable protocol and so any data I add will be able to be converted to JSON. This is then 'mapped' (is that the right word?) to the array in the Expenses class(which is not quite like a normal array in the sense that it can be broken down to different data types as defined by the struct). If I understand correctly, when the user enters an expense, this should then be saved into UserDefaults, which means that it should be there when I return to the app after shutting down (if not it will presumably return an empty array (empty screen). Please feel free to correct me if I have got any of this wrong.

Whilst I think I understand some of what's going on, I do have a couple of questions. Firstly, why are we encoding and decoding JSON data here? I understand the need for it when you have an external document in JSON format that you import into the app. You want to convert it to user friendly text but in this programme we don't have any external documentation in JSON format, so why are we converting to JSON, saving it to user defaults then converting it back? Or is that not what's happening here? I feel like I'm missing something. If we don't need to use JSON with user defaults then why are we?

My second question is, if it is not already answered in response to the first, what exactly is User Defaults? Is it some kind of dictionary? And what is the key "Items" used for? I don't want to suddenly jump from JSON to User Defaults, it is just that I think I have merged them together in my head and realise now that my confusion was actually mulitifaceted -JSON, User Defaults and the interaction between the two.


struct ExpenseItem: Identifiable, Codable {
    var id = UUID()
    let name: String
    let type: String
    let amount: Double
}
//expense items array needs to conform to Expenses protocol
class Expenses: ObservableObject {
  //this  property observer makes sure change announcements get sent whenever items array gets modified
    @Published var items = [ExpenseItem]() {
        didSet {
            if let encoded = try? JSONEncoder().encode(items) {
                UserDefaults.standard.set(encoded, forKey: "Items")
            }
        }
    }

  var personalItems: [ExpenseItem] {
    items.filter { $0.type == "Personal"}
  }
  var businessItems: [ExpenseItem] {
    items.filter { $0.type == "Business"}
  }

    init() {
        if let savedItems = UserDefaults.standard.data(forKey: "Items") {
            if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) {
                items = decodedItems
                return
            }
        }

        items = []
    }
}

2      

It's great to see the lights come on!

@cj concludes:

when the user enters an expense, this should then be saved into UserDefaults,
which means that it should be there when I return to the app after shutting down

Yes!

Data Exchange Format

Remember, first and foremost (from above), think of JSON as a data exchange format.

So, who are you exchanging data with?

You aren't exchanging data with Obelix, nor with @roosterboy. You are exchanging data with future you.

In your application, you have a great Struct for one ExpenseItem. Those are stored in a collection of expenses. The collection is displayed in a nice user interface.

But at some time, you'll have to put your iPhone down then go outside and touch some grass. So yeah, you need a strategy for saving all your expenses (in some format) and save them. At a future time, you'll want to grab those saved expenses and read them back into your application.

Saving Application Data

As I recall, the structure of an iPhone application provides a private storage area where your application can save preferences (like what color you want for a background, or what cities you want in your Weather application). This private area can also be used to save data important to your user. You should consider differerent strategies depending on how much data you want to actually save.

For example, if you're saving how many pushups you do each day, this could be a very small amount of data consisting of a date and an integer. You could save several years of data and not use very much space.

On the other hand, if you're saving sound bites from course work, or riffs from jam sessions with your musician friends, your sound files may be huge and you'll be using a ton of storage. You may need to explore your options and pick an appropriate strategy.

For this simple application, iExpense, you're just storing some imaginary expenses for a few imaginary purchases. For this lesson, @twoStraws selected the application's UserDefault storage area to store your expense data.

What are you storing in UserDefaults

Again, as a teaching technique, @twoStraws is walking you through example code where you grab all the expenses in your application, convert them into a single JSON object, then save that JSON object to UserDefaults.

The file will stay in UserDefaults until you read it back at some point, add new expenses to it, and save it again.

@cj misses the point here:

in this programme we don't have any external documentation in JSON format,
so why are we converting to JSON, saving it to user defaults then converting it back?

In short, you are exchanging a JSON file with future cj and storing the JSON data on your iPhone in a private area called UserDefaults that's protected, and only available to this single application.

Alternate Storage Techniques

To be fair, you could save your expense data as a list of comma separated values (excel spreadsheet style).
Or you could save your expense data in XML (extensible markup language).
Have you covered CoreData? You could use CoreData to store your expenses. Or FireBase.

There are a number of strategies you could use to gather all your expenses, and save them to your iPhone. The downside is how do you read the comma separated values file? How do you match the third data element in the seventh line to your expense category? These are programming issues that are solved by using a convenient API built into the Swift language.

2      

@CJ  

Thank you @Obelix again for such an in depth explanantion. I understood the idea of saving data to come back to at a later date, I was mostly just confused as to why it needed to be converted to JSON and couldn't just be stored as a swift file that was all. I'm guessing User Defaults only accepts certain data types. I haven't covered Core Data yet so it is possible that as I become familiar with different formats these things will make more sense.

I am still confused by what the "Items" key is for though. I don't see it anywhere else in the program. Is it just a placeholder word? Sorry for all the questions. I think that is the only one that is really bugging me now though. I feel much less confused than I was at the beginning of the post!

2      

Paul had touched upon UserDefaults in the previous project iExpense - UserDefaults

UserDefaults is intended to be used to store only small amount of data, of simple types.

Why use JSON in this case? I think it is just to practice encoding and decoding simple JSON, reading into and saving from a simple data structure. JSON is used extensively elsewhere, so this is a taster of what you may encounter if you have to decode / encode JSON externally.

The key "Items" is used in this line, to prepare access to the savedItems in UserDefaults.

if let savedItems = UserDefaults.standard.data(forKey: "Items") {

2      

@CJ  

Thank you @Greenamberred - yes, that's helpful. I have watched that, I think I just was a bit overwhelmed by the amount of new information in the project, that not everything stuck. In the example he uses the key in the property observer, so I guess that makes sense. I'm thinking maybe the key in the iexpenses is simply used to encode and decode the data and doesn't need to be used anywhere else. Sorry, probably just confusing myself now!

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.