JavaScript Object Notation, or JSON, is something that started popping up more and more the moment I started doing (Net)DevOps. These are some of my notes and examples.

JSON overview

JSON is an open standard format that can be used to store or transmit data. The two data structures available in JSON are:

  • Objects: unordered collection of one or more key-value pairs enclosed in braces {..}.
  • Arrays: ordered collection of values enclosed in brackets [..].

An object is similar to a Python dictionary and an array is similar to a Python list.

The values that you can use or come across in these data structures are the following:

  • String (0 or more Unicode characters)
  • Number (integer or float)
  • true
  • false
  • null
  • Object
  • Array

JSON basics in Python

There are multiple options available, but the only module I ever used is the one that is found in the Python Standard Library simply called json. The following Python 3.6 examples all use this library.

Dump a Python dictionary as JSON in a file

In the following example script, we use dump to serialize a dictionary to be stored as JSON in a file:

#!/usr/bin/python3
from json import dump

d = {
    'Automate the Boring Stuff with Python' : 'Al Sweigart',
    'Fluent Python' : 'Luciano Ramalho',
    'Learning Python' : 'Mark Lutz',
    'Python Tricks' : 'Dan Bader',
}

with open('/var/tmp/dictionary.json', 'w') as f:
    dump(d, f)

Whenever we translate Python to JSON, we do so using the following conversion table:

Python data structures serializes to JSON
dict object
list, tuple array
str string
int, float number
True true
False false
None null

Note: any file opened using with is automatically closed when the script is done using the file.

Load JSON as a Python dictionary from a file

In the next example script, we turn to load to turn a file with JSON data into a Python dictionary:

#!/usr/bin/python3
from json import load

with open('/var/tmp/dictionary.json', 'r') as f:    
    d = load(f)

Whenever we load JSON in a Python script, the following conversion applies:

JSON value deserializes to Python
object dict
array list
string str
number int, float
true True
false False
null None

Emit JSON as string

Instead of using dump to store JSON data in a file, we can turn to dumps and serialize an object to a string:

#!/usr/bin/python3
from json import load, dumps

with open('/var/tmp/dictionary.json', 'r') as f:    
    d = load(f)

s = dumps(d)

print(s)

JSON kwargs to make the output prettier

We can use the indent and separators keyword to increase the readability of any JSON we are working with. The keywords can be passed to the dump as well as the dumps method:

#!/usr/bin/python3
from json import dumps

d = {
    "JSON": [
        'is case-sensitive',
        'does not care about whitespaces',
        'does not offer a way to put in comments',
        'is also valid YAML',
        ], 
    }
# Print the original:
print(dumps(d))
# Print the pretty output:
print(dumps(d, indent=4, separators=(',', ': ')))

When we run the script, you can see that the readability of the JSON is drastically improved. The first line in the output is the ‘raw’ JSON and after this, you can see how the same JSON is printed when the keyword arguments indent and separators are used:

{"JSON": ["is case-sensitive", "does not care about whitespaces", "does not offer a way to put in comments", "is also valid YAML"]}

{
    "JSON": [
        "is case-sensitive",
        "does not care about whitespaces",
        "does not offer a way to put in comments",
        "is also valid YAML"
    ]
}

JSON kwargs to sort the output:

Setting sort_keys to True will output dictionaries with their keys sorted:

#!/usr/bin/python3
from json import dumps

d = {
    "dict z": {"b": "b", "a": "a", "c": "c"},
    "dict b": {"d": "d", "h": "h"},
    "dict a": {"k": "k", "a": "a"}, 
    "dict c": [ 'c', 'b', 'a', ]
}

# Sort it by keys and print it:
print(dumps(d, sort_keys=True, indent=4, separators=(',', ': ')))

This will output the following:

{
    "dict a": {
        "a": "a",
        "k": "k"
    },
    "dict b": {
        "d": "d",
        "h": "h"
    },
    "dict c": [
        "c",
        "b",
        "a"
    ],
    "dict z": {
        "a": "a",
        "b": "b",
        "c": "c"
    }
}

The dictionaries were sorted, the list that was present in of the dictionaries was not.

Same as with indent and separators, this works for dump as well as dumps.

Dictify a web page

In this example script, we use urlopen from urllib.request to open a URL. We read the return and load it using json.loads:

#!/usr/bin/python3
import json
from pprint import pprint
from urllib.request import urlopen

# Open the web page:
html_content = urlopen('http://validate.jsontest.com/?json=%5BJSON-code-to-validate%5D').read()  

# Load the JSON:
d_json_test = json.loads(html_content)

# Print the JSON to screen:
pprint(d_json_test)

# All in one line:
pprint(json.loads(urlopen('http://validate.jsontest.com/?json=%5BJSON-code-to-validate%5D').read()))

If we run the script, the last line outputs the following:

{'empty': False,
 'object_or_array': 'array',
 'parse_time_nanoseconds': 29928,
 'size': 1,
 'validate': True}

Using JSON in jinja

In this last example, we use json.loads to deserialize JSON from a string, and then use the resulting dictionary in a Jinja template:

#!/usr/bin/python3
import json
from jinja2 import Template

json_str = '''
    [
    {"hostname": "ny01", "mgmt-ip": "10.0.0.1" }, 
    {"hostname": "ny02", "mgmt-ip": "10.0.0.2" }, 
    {"hostname": "ny03", "mgmt-ip": "10.0.0.3" }, 
    {"hostname": "ny04", "mgmt-ip": "10.0.0.4" }
    ]
    '''
# Load json from string:
routers = json.loads(json_str)

# Define template:
template = Template('''

{% for router in routers %}
set system host-name {{ router['hostname'] }}
set interfaces lo0 unit 0 family inet address {{ router['mgmt-ip'] }} primary
{% endfor %}

''')

# Render template and send the output to screen:
print(template.render(routers = routers))

Running the previous script will output the following:

set system host-name ny01
set interfaces lo0 unit 0 family inet address 10.0.0.1 primary

set system host-name ny02
set interfaces lo0 unit 0 family inet address 10.0.0.2 primary

set system host-name ny03
set interfaces lo0 unit 0 family inet address 10.0.0.3 primary

set system host-name ny04
set interfaces lo0 unit 0 family inet address 10.0.0.4 primary