Skip to content

Easy Json

py_simple.easy_json

easy_json is built to simplify working with json files

EasyJsonError

Bases: Exception

Raised when a JSON file can't be opened or parsed.

Wraps the underlying error (missing file, bad permissions, invalid JSON syntax, etc.) so py_simple functions can fail with one consistent, easy-to-read exception instead of a random builtin one.

Parameters:

Name Type Description Default
message str

Human-readable description of what went wrong.

required

compare_json(data1=None, data2=None, filepath1=None, filepath2=None, path='')

Compares two JSON objects or files and returns a dictionary describing the differences: added keys, removed keys, and changed values. Supports comparison of both dictionaries and lists. Nested structures are traversed recursively.

Provide exactly one of data arguments or filepath arguments — not both for each side.

Parameters:

Name Type Description Default
data1 dict | list

First JSON object to compare.

None
data2 dict | list

Second JSON object to compare.

None
filepath1 str

Path to the first JSON file.

None
filepath2 str

Path to the second JSON file.

None
path str

Internal use only — the current dot-notation path during recursion.

''

Returns:

Name Type Description
dict dict

A dictionary with three keys: - added: dict of keys present in data2 but not in data1. - removed: dict of keys present in data1 but not in data2. - changed: dict of keys whose values differ between the two objects. All nested differences use dot-notation keys (e.g. user.address.city).

Raises:

Type Description
EasyJsonError

If neither or both of data/filepath are provided for either side, or if the JSON data is not a dict or list.

Example
from py_simple import compare_json

diff = compare_json(
    data1={"name": "Sara", "age": 25},
    data2={"name": "Sara", "age": 26, "city": "NYC"}
)
print(diff)
# {"added": {"city": "NYC"}, "removed": {}, "changed": {"age": (25, 26)}}
def compare_dicts(d1, d2, path=""):
    result = {"added": {}, "removed": {}, "changed": {}}
    all_keys = set(d1.keys()) | set(d2.keys())
    for key in all_keys:
        current_path = f"{path}.{key}" if path else key
        if key not in d2:
            result["removed"][current_path] = d1[key]
        elif key not in d1:
            result["added"][current_path] = d2[key]
        elif d1[key] != d2[key]:
            result["changed"][current_path] = (d1[key], d2[key])
    return result

flatten_json(seperator='-', data=None, filepath=None)

Flattens a nested dictionary or JSON file into a single-level dictionary, joining nested keys with seperator.

Handles dicts nested inside dicts, lists nested inside dicts, and dicts nested inside lists, at any depth. List items are joined using their index (e.g. b-0, b-1). Provide exactly one of data or filepath — not both.

Parameters:

Name Type Description Default
seperator str

String used to join nested keys together. Defaults to "-".

'-'
data dict

A dictionary to flatten.

None
filepath str

Path to a JSON file to flatten.

None

Returns:

Type Description
dict | None

dict | None: A single-level dictionary with all nested values unwrapped into flat, uniquely-named keys.

Raises:

Type Description
EasyJsonError

If neither or both of data/filepath are provided, or if the file can't be opened or parsed.

Example
from py_simple import flatten_json

flatten_json(data={"a": 1, "b": {"c": 2}})
# -> {"a": 1, "b-c": 2}
from benedict import benedict

d = benedict({"a": 1, "b": {"c": 2}})
flat = dict(d.flatten("-"))
# still need to manually unwrap dicts/lists further

get_json_keys(data=None, filepath=None)

Returns the top-level keys in a dictionary or JSON file.

Provide exactly one of data or filepath. This is useful when you want to quickly see what information a JSON object contains before reading a specific value.

Parameters:

Name Type Description Default
data dict

A dictionary whose keys should be returned.

None
filepath str

Path to a JSON file whose top-level keys should be returned.

None

Returns:

Type Description
list[str]

list[str]: The dictionary's top-level keys, in their original order.

Raises:

Type Description
EasyJsonError

If neither or both of data/filepath are provided, or if the JSON data is not a dictionary.

Example
from py_simple.easy_json import get_json_keys

get_json_keys(data={"name": "Sara", "active": True})
# -> ["name", "active"]
data = {"name": "Sara", "active": True}
keys = list(data.keys())

get_nested(data, path, default=None)

Safely retrieve a value from nested JSON/dict/list using dot notation.

Parameters:

Name Type Description Default
data dict | list

Parsed JSON data.

required
path str

Dot-separated path (e.g., "user.address.city" or "items.0.name").

required
default Any

Value to return if path not found. Defaults to None.

None

Returns:

Name Type Description
Any

Value at the path, or default.

is_json_file(filepath)

Checks whether a filepath points to an existing file with a .json extension.

Parameters:

Name Type Description Default
filepath str

Path to check.

required

Returns:

Name Type Description
bool bool

True if the file exists and ends in .json, False otherwise.

Example
from py_simple import is_json_file

if is_json_file("config.json"):
    print("Looks good!")
import os

filepath = "config.json"
if os.path.isfile(filepath) and filepath.split(".")[-1] == "json":
    print("Looks good!")

is_nested_json(data=None, filepath=None)

Checks whether a dictionary or JSON file contains any nested dictionaries or lists at the top level.

Provide exactly one of data or filepath — not both.

Parameters:

Name Type Description Default
data dict

A dictionary to check for nested structures.

None
filepath str

Path to a JSON file to check for nested structures.

None

Returns:

Type Description
bool | None

bool | None: True if any top-level value is a dict or list, False if all top-level values are flat.

Raises:

Type Description
EasyJsonError

If neither or both of data/filepath are provided, or if the file can't be opened or parsed.

Example
from py_simple import is_nested_json

is_nested_json(data={"a": 1, "b": {"c": 2}})  # -> True
is_nested_json(data={"a": 1, "b": 2})  # -> False
data = {"a": 1, "b": {"c": 2}}
is_nested = any(
    isinstance(v, (dict, list)) for v in data.values()
)

open_json(filepath)

Opens a JSON file and returns its contents as a dictionary.

Parameters:

Name Type Description Default
filepath str

Path to the JSON file to open.

required

Returns:

Type Description
dict | None

dict | None: The parsed JSON contents as a dictionary.

Example
from py_simple import open_json

data = open_json("config.json")
print(data["name"])
import json

try:
    with open("config.json", encoding="utf-8") as json_file:
        data = json.load(json_file)
    print(data["name"])
except Exception as e:
    print(f"Couldn't read the file: {e}")

pretty_json(data=None, filepath=None)

Returns a pretty-printed, indented JSON string from a dictionary or a JSON file. Provide exactly one of data or filepath — not both.

Parameters:

Name Type Description Default
data dict

A dictionary to format as pretty-printed JSON.

None
filepath str

Path to a JSON file to load and format as pretty-printed JSON.

None
Example
from py_simple import pretty_json

print(pretty_json(data={"name": "Sara"}))
import json

print(json.dumps({"name": "Sara"}, indent=2))

save_json_data(filepath, data)

Saves a dictionary to a JSON file. Raises an error if the file already exists, so you don't accidentally overwrite something.

Parameters:

Name Type Description Default
filepath str

Path to the JSON file to create.

required
data dict

The data to save.

required
Example
from py_simple import save_json_data

save_json_data("config.json", {"name": "Sara"})
import json
import os

filepath = "config.json"
if os.path.exists(filepath):
    print(f"File {filepath} already exists.")
else:
    with open(filepath, "w") as json_file:
        json.dump({"name": "Sara"}, json_file, indent=4)

update_json(filepath, new_data)

Updates a JSON file with new data, merging it into what's already there. Existing top-level keys in new_data overwrite matching keys in the file; anything else in the file is left untouched.

Parameters:

Name Type Description Default
filepath str

Path to the JSON file to update.

required
new_data dict

The data to merge into the existing file.

required
Example
from py_simple import update_json

update_json("config.json", {"name": "Sara"})
import json

with open("config.json") as json_file:
    data = json.load(json_file)

data.update({"name": "Sara"})

with open("config.json", "w") as json_file:
    json.dump(data, json_file, indent=4)