Introduction to JSON

Updated on 28 Dec 2022

Introduction

Json is a way to represent data, and if often used inconjunction with JavaScript and other programming languages. Infact the acronym stands for JavaScript Object Notation. Unlike XML, it generally consumes less space, however it does not cope with the concept of XML attributes very well.

You can see the equivalent representation of this in the xml introduction section.

{
    "country": [
        {   
            "name": "Liechtenstein",
            "rank": 1,
            "year": 2009,
            "gdppc": 141100,
            "neighbour": [
                {
                    "name": "Austria",
                    "direction": "E"
                },
                {
                    "name": "Switzerland",
                    "direction": "W"
                }
            ]
        },
        {   
            "name": "Singapore",
            "rank": 4,
            "year": 2011,
            "gdppc": 59900,
            "neighbour": [
                {
                    "name": "Malaysia",
                    "direction": "N"
                }
            ]
        },
        {
            "name": "Panama",
            "rank": 68,
            "year": 2011,
            "gdppc": 13600,
            "neighbour": [
                {
                    "name": "Costa Rica",
                    "direction": "W"
                },
                {
                    "name": "Colombia",
                    "direction": "E"
                }
            ]
        }
    ]
}

Loading a Json file

Loading a Json object can be done with the following methods:

  • json.loads -> from a string
  • json.load -> from a stream / file

Note that with load, you don’t supply a filename to load, but rather a file stream. Example is shown below:

import json

with open('json_data1.json', 'r') as f:
    myData = json.load(f)

print(myData["country"][0]["name"])

Loading a Json string

It is possible that you might have a Json string. This could come about from a database query, api call or even your own code. Here is an example of how we might use a loads to load json string.

import json

# read the json file into a string
f = open('json_data1.json')
json_lines = f.read()
f.close()

# use json.loads to load json string into dict
myData = json.loads(json_lines)

print(myData["country"][1]["name"])

Saving a List or Dict to Json string or file

It is possible to save a Dict to a Json file or string.

  • json.dumps -> Converts a Dict or Array to Json.
  • json.dump -> Saves a Dict or Array as Json to a file.

Consider these basic examples.

import json

records = [
    ['brent', 'High Distinction'],
    ['paul', 'Pass'],
    ['andrea', 'Credit']
]

json_string = json.dumps(records)
print(json_string)

Saving the Json to file

import json

records = [
    ['brent', 'High Distinction'],
    ['paul', 'Pass'],
    ['andrea', 'Credit']
]

with open('json_data2.json', 'w') as f:
    json.dump(records, f)

Tip: the s in load and dump is for string. I.e. load from string or dump to string.

Json - guided exercise

In our example for Classes we ended up with a Quote.py that had a list of quotes. There are 2 things that I want to do:

  • expand the Quote class to include a rating and group section
  • read and save the quotes, rating and group to a file
  • do all this using json

Solution

First step is to have a look at how we are storing the Quotes in the class, and modify that to how we want to store the quotes.

# this is a constructor
    def __init__(self):  
        self.quotes = [
            'git',
            'imbecile',
            'invented TIM',
            'need to ask yourself, what are we really trying to solve'
        ]    # instance variable

I think we can change this to something like this:

    # this is a constructor
    def __init__(self):  
        self.quotes = [
            {
                "quote": "git",
                "rating": 1,
                "group": "tech"
            },
            {
                "quote": "imbecile",
                "rating": 3,
                "group": "insults"
            },
            {
                "quote": "invented TIM",
                "rating": 3,
                "group": "tech"
            },
            {
                "quote": "need to ask yourself, what are we really trying to solve",
                "rating": 7,
                "group": "insults"
            }
        ]    # instance variable

Now if we used Quote.py in the same manner as before, we get something interesting for the random_quote.

import Quote

# 1st Quote is the filename
# 2nd Quote is the classname
# random_quote is the method in the Quote class

myQuotes = Quote.Quote() 
print(myQuotes.random_quote())

We get the whole section, i.e. quote, rating and group. If you really just wanted to get the group, you can modify your code like so:

print(myQuotes.random_quote()['group'])

add_quote - good example

The current append method for the quotes list works as is, and will work perfectly well for us if we adhere to the rules. I.e. we add_quote that matches the current signature; quote, rating, group.

import Quote

# 1st Quote is the filename
# 2nd Quote is the classname
# random_quote is the method in the Quote class

myQuotes = Quote.Quote() 

myQuotes.add_quote({'quote': 'added this myself!', 'rating': 20, 'group': 'insults'})
print(myQuotes.get_all_quotes())

add_quote - bad example

If however we simply use the existing code from the classes section then things don’t quite work out for us.

import Quote

# 1st Quote is the filename
# 2nd Quote is the classname
# random_quote is the method in the Quote class

myQuotes = Quote.Quote() 

myQuotes.add_quote('added this myself!')
print(myQuotes.get_all_quotes())

We can see here that when we added our quote, it is essentially unstructured. That means using code like this: print(myQuotes.random_quote()['group']) will definitely fail if it picks out the last quote because there is no group (or quote or rating).

Converting existing quotes to Json

Next step is to convert the existing quote data to a Json file, and load that in our Quote class. For this I basically cut and paste the data out of Quotes.py and pasted it into json_quotes.json.

json_quotes.json

[
    {
        "quote": "git",
        "rating": 1,
        "group": "tech"
    },
    {
        "quote": "imbecile",
        "rating": 3,
        "group": "insults"
    },
    {
        "quote": "invented TIM",
        "rating": 3,
        "group": "tech"
    },
    {
        "quote": "need to ask yourself, what are we really trying to solve",
        "rating": 7,
        "group": "insults"
    }
]

Updating the Quotes.py file

So I don’t get muddled up with my earlier stuff, I’ll create a new file called Quotes2.py to hold all our new modifications to the class.

Quotes2.py

import random
import json

class Quote:
    staticVar = 'this is a class variable'

    # this is a constructor
    def __init__(self):  
        with open('json_quotes.json', 'r') as f:
            self.quotes = json.load(f)
.
.
.

Testing the new Quotes class

json-guided3.py

import Quote2

# 1st Quote2 is the filename
# 2nd Quote is the classname
# random_quote is the method in the Quote class

myQuotes = Quote2.Quote() 

# myQuotes.add_quote('added this myself!')
myQuotes.add_quote({'quote': 'added this myself!', 'rating': 20, 'group': 'insults'})
print(myQuotes.get_all_quotes())

Saving added quotes

Next step is to modify the add_quote function so that we can save the added quote to the json file.

def add_quote(self, new_quote):
    self.quotes.append(new_quote)

    with open('json_quotes.json', 'w') as f:
        json.dump(self.quotes, f, indent=4)

I have used indent=4 which saves the json data in pretty format. Remove this parameter and rerun the json-guided3.py to see how it affected the json_quotes.json file.

Full version of Quote2.py

import random
import json

class Quote:
    staticVar = 'this is a class variable'

    # this is a constructor
    def __init__(self):  
        with open('json_quotes.json', 'r') as f:
            self.quotes = json.load(f)
         
    def random_quote(self):
        return random.choice(self.quotes)

    def add_quote(self, new_quote):
        self.quotes.append(new_quote)

        with open('json_quotes.json', 'w') as f:
            json.dump(self.quotes, f, indent=4)

    def get_all_quotes(self):
        return self.quotes

if __name__ == "__main__":
    print('this should be imported into a main application...')

Extended exercise 1

If we run json-guided3.py a few more times we’ll notice that the add_quote function will continually add the same quote to the file. Use your knowledge that you’ve gained so far to see if you can prevent the same quote from being added to the json_quotes.json file.

Extended exercise 2

We might change the data structure for our random_quotes, and it might look something like this now.

{
    "tech": [
        {
            "quote": "git",
            "rating": 1
        },
        {
            "quote": "invented TIM",
            "rating": 3
        }
    ],
    "insults": [
        {
            "quote": "imbecile",
            "rating": 3
        }, 
        {
            "quote": "need to ask yourself, what are we really trying to solve",
            "rating": 7
        }
    ]
}

Create a new Quote3.py file (from Quote2.py) that can work with this new structure.

Hint, you might try writing some sample code that can access the quotes first before trying to modify the class.