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_argsmethod 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=programmingremoves the first;~!page.tags=programmingremoves 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
964f8e11e3a4f29cf7c62ecd6ca05a9ef9abddf36604814ccf5b46c7a4d3fc12
|
|
| MD5 |
c8a39a2bf2caec2778b887ae1293a937
|
|
| BLAKE2b-256 |
beaf2053f8df098b21a1ad3ae0b10f669939358103016f6a07f41fc822b12db7
|
Provenance
The following attestation bundles were made for json_castle-0.1.1.tar.gz:
Publisher:
publish-release.yml on marianpekar/json-castle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
json_castle-0.1.1.tar.gz -
Subject digest:
964f8e11e3a4f29cf7c62ecd6ca05a9ef9abddf36604814ccf5b46c7a4d3fc12 - Sigstore transparency entry: 1052960572
- Sigstore integration time:
-
Permalink:
marianpekar/json-castle@334c90586ef115ca558da7189549993651904bf3 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/marianpekar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-release.yml@334c90586ef115ca558da7189549993651904bf3 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa46227e1ecdd8ef39dd750566153bd8944b16c4c807cd28c374b5bc6a4727f5
|
|
| MD5 |
52dce1c5ed43534182b562ae85afef3f
|
|
| BLAKE2b-256 |
d157dff752cae9fc2d09b8108ce9a2fe878dcdaf7343905739266e96d81c8489
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
json_castle-0.1.1-py3-none-any.whl -
Subject digest:
aa46227e1ecdd8ef39dd750566153bd8944b16c4c807cd28c374b5bc6a4727f5 - Sigstore transparency entry: 1052960654
- Sigstore integration time:
-
Permalink:
marianpekar/json-castle@334c90586ef115ca558da7189549993651904bf3 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/marianpekar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-release.yml@334c90586ef115ca558da7189549993651904bf3 -
Trigger Event:
release
-
Statement type: