Skip to main content

Built on top of native json module for deserialization from JSON to data classes with additional support for nested objects with variables, environment variables, and post-load overrides.

Project description

JsonCastle

JsonCastle is a Python module built on top of the native json module for deserialization from JSON to data classes with additional support for:

  • nested objects
  • JSON nariables
  • environment variables
  • post-load overrides, including overriding, adding, and removing collection elements via special CLI argument syntax like node.next_node.number=2, ~node.tags[0], +node.next_node.tags=foo, ~node.next_node.tags=buzz, etc.

With these features, you might find it especially useful in CI/CD pipelines.

Getting Started

Imagine you have a JSON configuration file for an automated process, but you want to run this process with slightly different variants of this file. Typically, you'd end up with multiple very similar configuration files, or you'd create an ad-hoc solution that would allow you to override certain parameters via CLI arguments. JsonCastle gives you such a solution out of the box for all properties of your data scheme, even for those in nested objects.

Since native json doesn't support variables, such configuration files might have a lot of repeated strings. Editing such files can be tedious and error-prone. You write a JSON file with variables and environment variables as the following example:

{
    "$home_path": "%HOME%",
    "$package_path": "${home_path}debug",
    "exe_path": "${package_path}/mytool.exe",
    "node": {
        "number": 0,
        "tags": ["foo", "bar"],
        "next_node": {
            "number": 1,
            "tags": ["fizz", "buzz"]
        }
    }
}

Assuming the HOME environment variable is set, for example, to C:/users/cicd/, then the value of the $package_path variable will be expanded from ${home_path}debug to C:/users/cicd/debug, and the value of exe_path will be C:/users/cicd/debug/mytool.exe.

In Python, you'd define your data classes that match the scheme:

from dataclasses import dataclass

@dataclass
class Cfg:
    exe_path: str = None
    node: Node = None

@dataclass
class Node:
    number: int
    tags: list[str]
    processed: False
    next_node: Node = None

Now you can use the JsonCastle.load_from_file method to read and parse example.json and get an instance of the Cfg class:

from pprint import pprint
import sys

from json_castle import JsonCastle

cfg = JsonCastle.load_from_file(Cfg, "example.json", **JsonCastle.parse_args(sys.argv))
pprint(cfg)

If you run the Python script without any arguments, you’d see cfg printed as expected:

Cfg(exe_path='C:/users/cicd/debug/mytool.exe',
    node={'next_node': {'number': 1, 'tags': ['fizz', 'buzz']},
          'number': 0,
          'tags': ['foo', 'bar']})

However, by passing **JsonCastle.parse_args(sys.argv) as the third argument, you can override any values post-load via CLI. Running the script again with the arguments node.number=1 ~node.tags[0] node.next_node.number=2 +node.next_node.tags=foo ~node.next_node.tags=buzz will give you a cfg instance like this:

Cfg(exe_path='C:/users/cicd/debug/mytool.exe',
    node={'next_node': {'number': 2, 'tags': ['fizz', 'foo']},
          'number': 1,
          'tags': ['bar']})

If you want to deserialize your JSON from a string stream, you can use the static method load(cls, stream: IO[str], **kwargs), the usage is the same as of the load_from_file(cls, path: str, **kwargs) in the example above, but you provide your stream as the second argument instead of filepath.

CLI Overrides Syntax

Although some of the syntax for post-load overrides via the CLI was introduced already in the Getting Started section, here's an overview of all options with examples.

Basic Syntax

JsonCastle.parse_args method accepts a list of strings, where each element is a key=value pair except the one for removing an item by index, that only has a key, in which case, in the dictionary the method returns, the value is None.

Of course, you don't have to use this method and construct a dictionary by yourself instead; the JsonCastle.parse_args just provides you a convenient way for post-load overriding via CLI, which is particularly useful in CI/CD pipelines. Here are some examples of CLI args for overriding top-level pairs:

age=27 name=John full_name="John Smith" active=true balance=123.45

Nested Objects

If you need to override a nested value, you specify a path, separating objects with ., as you can see in the Getting Started. The following example shows how to override several pairs nested in the person object.

person.age=27 person.name=John person.full_name="John Smith" person.active=true person.balance=123.45

Working with Collections

When it comes to collections, you can change, add, or remove an item as well. If the collection is a nested object, the rules are the same as described in the Nested Objects section.

Overriding an Item

To override an item, you provide a path to the collection, in square brackets an index of the item you wish to override, and a new value on the right side of the = symbol. The following example shows how to change the value of the first element of the tags collection nested in the page object to programming.

page.tags[0]=programming

Adding a new Item

When you wish to add a new item to a collection, you prefix the key=value pair with a + symbol. The following example shows how to add a new value of python to the tags collection nested in the page object.

+page.tags=python

Removing an Item by Index

If you wish to remove an item at specific index, you prefix a path to the collection with index in square brackets of the element you wish to remove with the ~ symbol. The following example shows how to remove the second item from the tags collection nested in the page object.

~page.tags[1]

If any items follow the removed one, they will be pushed down, and the new length of the collection will be n-1.

Removing an Item by Value

If you wish to remove an item of a specific value, instead of the index as described in Removing an Item by Index, you provide a key=value pair prefixed with ~. The following example shows how to remove an item with a value of “programming” from the tags collection nested in the page object.

~page.tags=programming

If there is more than one occurence if programming string in the collection, only the first one will be removed.

Unit Tests

Unit Tests are not just a great way to ensure nothing is broken when a new feature is added, but also for implementing new features using TDD, which is the recommended way for extending this library. These tests can also serve as an overview of what the library is capable of. You can find all tests in the unit_test.py file.

Future Ideas

  • Add support for conditionals, loops, and mathematical expressions in JSON.
  • Extend the parse_args method to allow adding a custom object to a collection.
  • Add regex support for removing items by value.
  • When removing an item from a collection by value, let the user decide whether they want to remove just one or all items that match the value (~page.tags=programming removes the first; ~!page.tags=programming removes all).
  • Add support for removing items from a numerical collection by conditions >, <, <= , or =>.
  • Add support for removing items by range (i.e. ~items[1:4] would remove items at indices 1, 2, 3 and 4).
  • Add support for removing custom items by condition (i.e., ~people={age < 16} would remove from the people collection all objects with the age key-value pair with age less than 16).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

json_castle-0.1.1.tar.gz (8.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

json_castle-0.1.1-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

Details for the file json_castle-0.1.1.tar.gz.

File metadata

  • Download URL: json_castle-0.1.1.tar.gz
  • Upload date:
  • Size: 8.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for json_castle-0.1.1.tar.gz
Algorithm Hash digest
SHA256 964f8e11e3a4f29cf7c62ecd6ca05a9ef9abddf36604814ccf5b46c7a4d3fc12
MD5 c8a39a2bf2caec2778b887ae1293a937
BLAKE2b-256 beaf2053f8df098b21a1ad3ae0b10f669939358103016f6a07f41fc822b12db7

See more details on using hashes here.

Provenance

The following attestation bundles were made for json_castle-0.1.1.tar.gz:

Publisher: publish-release.yml on marianpekar/json-castle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file json_castle-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: json_castle-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 7.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for json_castle-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 aa46227e1ecdd8ef39dd750566153bd8944b16c4c807cd28c374b5bc6a4727f5
MD5 52dce1c5ed43534182b562ae85afef3f
BLAKE2b-256 d157dff752cae9fc2d09b8108ce9a2fe878dcdaf7343905739266e96d81c8489

See more details on using hashes here.

Provenance

The following attestation bundles were made for json_castle-0.1.1-py3-none-any.whl:

Publisher: publish-release.yml on marianpekar/json-castle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page