Python How to convert a nested JSON to a class

eye-catch Python

It’s easy to convert an object to JSON and JSON to an object in Python. json module provides the following functions.

  • dumps: object -> JSON
  • loads: JSON -> object
import json

version_info = {
    "major": 2,
    "minor": 1,
    "patch": 0,
}
version_json = json.dumps(version_info, indent=2)
print(version_json)
# {
#   "major": 2,
#   "minor": 1,
#   "patch": 0
# }

version_dict = json.loads(version_json)
print(version_dict)
# {'major': 2, 'minor': 1, 'patch': 0}
print(f"major: {version_dict['major']}, minor: {version_dict.get('minor')}, patch: {version_dict.get('patch')}")

json.loads converts the specified object to a dictionary. Therefore, we can read the desired value by giving the target key name in the above way.

If it doesn’t look good for you, we can implement it in the way below instead.

import json
from types import SimpleNamespace

version_obj = json.loads(version_json, object_hook=lambda d: SimpleNamespace(**d))
print(f"major: {version_obj.major}, minor: {version_obj.minor}, patch: {version_obj.patch}")
# major: 2, minor: 1, patch: 0

In this way, the square brackets or get function is not needed.

It’s totally fine if the converted object is used in a small scope. However, if we need to read the value many times in many places, we might make a typo for the target key name because IntelliSense doesn’t support typing here.

If the JSON is converted to a class, IntelliSense supports the typing and it’s easier to code. How can we achieve this?

Sponsored links

Define a class that have the same name in the parameters

We have to define a class first. The class must have the same keys as the keys used in JSON data. In this example, it’s major, minor, and patch.

class Version:
    def __init__(self, major: int, minor: int, patch: int) -> None:
        self.major = major
        self.minor = minor
        self.patch = patch

Once a class is defined according to the JSON format, we can just pass the created dictionary to the class with double asterisks.

import json

version_dict = json.loads(version_json)
version = Version(**version_dict)
# IntelliSense works here
print(f"major: {version.major}, minor: {version.minor}, patch: {version.patch}")
# major: 2, minor: 1, patch: 0

IntelliSense knows the type of version variable, so it shows possible variable names and functions.

Sponsored links

How to convert a nested JSON to a class

Let’s consider a more complex example. Nested JSON.

import json

version_info = {
    "major": 2,
    "minor": 1,
    "patch": 0,
}

software_info = {
    "name": "my-software",
    "version": version_info,
    "availableVersions": ["2.1.0", "2.0.0", "1.0.0"],
}

software_json = json.dumps(software_info, indent=2)
print(software_json)
# {
#   "name": "my-software",
#   "version": {
#     "major": 2,
#     "minor": 1,
#     "patch": 0
#   },
#   "availableVersions": [
#     "2.1.0",
#     "2.0.0",
#     "1.0.0"
#   ]
# }

This JSON has version property in it. It is the same structure as the previous example. Let’s define the corresponding class first.

# NG: This doesn't work as expected
class Software:
    def __init__(
        self,
        name: str,
        version: Dict[str, Any],
        availableVersions: List[str], # can't be snake_case here
    ) -> None:
        self.name = name
        self.version = version
        self.available_versions = availableVersions

The parameter name can’t be snake_case but we can make it for the property by doing this. Let’s run it in the same way as before.

import json

software_dict = json.loads(software_json)
software = Software(**software_dict)
print(f"name: {software.name}")
# name: my-software
print(f"version: {software.version}, version.major: {software.version.major}")
# Traceback (most recent call last):
#   File "/workspaces/blogpost-python/src/json_load.py", line 103, in <module>
#     run()
#   File "/workspaces/blogpost-python/src/json_load.py", line 97, in run
#     print(f"version: {software.version}, version.major: {software.version.major}")
# AttributeError: 'dict' object has no attribute 'major'
print(f"availableVersions: {software.available_versions}, availableVersions[1]: {software.available_versions[1]}")

It throws an error. It tries to access major property but version variable isn’t a Version class. It’s actually a dictionary. So it must be either software.version['major'] or software.version.get('major).

To convert the property to a class, we must instantiate the class by using double asterisks here too.

class Software:
    def __init__(
        self,
        name: str,
        version: Dict[str, Any],
        availableVersions: List[str],
    ) -> None:
        self.name = name
        self.version = Version(**version) # use double asterisks here
        self.available_versions = availableVersions

Then, software.version becomes Version class and thus major property can be accessed.

import json

software_dict = json.loads(software_json)
software = Software(**software_dict)
print(f"name: {software.name}")
# name: my-software
print(f"version: {software.version}, version.major: {software.version.major}")
# version: <__main__.Version object at 0x7ffa2f8e50c0>, version.major: 2
print(f"availableVersions: {software.available_versions}, availableVersions[1]: {software.available_versions[1]}")
# availableVersions: ['2.1.0', '2.0.0', '1.0.0'], availableVersions[1]: 2.0.0

As you can see this, no special treatment is needed for array type.

Comments

Copied title and URL