A zero-dependency, deterministic JSON payload mapper that transforms source data into target structures using dotted paths and array indices.
Project description
✨ jsonshift
A lightweight Python package to convert one JSON payload into another using a declarative mapping spec defined in JSON.
Engine rules (designed for system integrations):
- If the source path does not exist → raises
MappingMissingError. - If the source value is
null/None→ the destination receivesNone(not replaced). defaultsonly fills values when the destination field is absent (does not overwriteNoneor existing values).- Supports dotted and indexed paths, e.g.
a.b[0].c. - Extended mapper (
ArrayMapper) supports wildcard list expansion using[*]. - NEW: Support for optional mappings using
optional: true.
🧩 Installation
pip install jsonshift
# or for development:
pip install -e .
🚀 Basic usage
from jsonshift import Mapper
payload = {
"customer_name": "John Doe",
"cpf": None,
"amount": 1000.0,
"installments": 12
}
spec = {
"map": {
"customer.name": "customer_name",
"customer.cpf": "cpf", # None is preserved
"contract.amount": "amount",
"contract.installments": "installments"
},
"defaults": {
"contract.type": "CCB",
"contract.origin": "ORQ"
}
}
out = Mapper().transform(spec, payload)
print(out)
# {
# "customer": {"name": "John Doe", "cpf": null},
# "contract": {"amount": 1000.0, "installments": 12, "type": "CCB", "origin": "ORQ"}
# }
🧠 ArrayMapper — mapping lists with [*]
The ArrayMapper extends Mapper and adds support for wildcard list mappings.
You can transform lists of objects using the same declarative spec syntax.
from jsonshift.array_mapper import ArrayMapper
payload = {
"products": [
{"id": "P-001", "name": "Notebook", "price": 4500.0, "stock": 12},
{"id": "P-002", "name": "Mouse Gamer", "price": 250.0, "stock": 100}
]
}
spec = {
"map": {
"new_products[*].code": "products[*].id",
"new_products[*].title": "products[*].name",
"new_products[*].price_brl": "products[*].price",
"new_products[*].available": "products[*].stock"
},
"defaults": {
"new_products[*].currency": "BRL"
}
}
out = ArrayMapper().transform(spec, payload)
print(out)
# {
# "new_products": [
# {"code": "P-001", "title": "Notebook", "price_brl": 4500.0, "available": 12, "currency": "BRL"},
# {"code": "P-002", "title": "Mouse Gamer", "price_brl": 250.0, "available": 100, "currency": "BRL"}
# ]
# }
🧩 The same number of elements is preserved — each input list item becomes one transformed output item.
Defaults with wildcards (new_products[*].currency) apply individually to every object in the list.
🆕 Optional fields (optional: true)
You can mark a mapping as optional, meaning:
- If the source path exists → it is mapped normally
- If the source path does NOT exist → it is silently ignored
- No
MappingMissingErroris raised
This is useful when transforming payloads with optional fields.
Example (simple object)
from jsonshift import Mapper
payload = {
"name": "John Doe"
}
spec = {
"map": {
"user.full_name": "name",
"user.nickname": {
"path": "nickname",
"optional": True
}
}
}
out = Mapper().transform(spec, payload)
print(out)
# {
# "user": {
# "full_name": "John Doe"
# }
# }
Example (inside arrays)
from jsonshift import ArrayMapper
payload = {
"users": [
{"id": 1},
{"id": 2, "phone": "9999"}
]
}
spec = {
"map": {
"items[*].phone": {
"path": "users[*].phone",
"optional": True
}
}
}
out = ArrayMapper().transform(spec, payload)
print(out)
# {
# "items": [
# {},
# {"phone": "9999"}
# ]
# }
Optional mappings never overwrite defaults, and defaults only apply when the destination field does not exist.
🖥️ Command-line interface (CLI)
# Transform using JSON files
jsonshift --spec spec.json --input payload.json
# or via stdin
cat payload.json | jsonshift --spec spec.json
▶️ Example run (from the repository)
This repository includes ready-to-use files under the examples/ folder.
# From the project root
jsonshift --spec examples/spec.json --input examples/payload.json
Expected output:
{
"customer": {
"name": "John Doe",
"cpf": "12345678901"
},
"contract": {
"amount": 1500.0,
"installments": 6,
"type": "CCB",
"origin": "ORQ"
}
}
📘 Spec format
{
"map": {
"destination.path": "source.path",
"destination[*].field": "source[*].field"
},
"defaults": {
"destination.path": "<fixed_value>",
"destination[*].field": "<fixed_value>"
}
}
⚠️ Error handling
MappingMissingError— source path not found (unlessoptional: true).InvalidDestinationPath— invalid destination (e.g., destination with index).TypeError— source list expected but got non-list in wildcard paths.
🧪 Testing
Run all unit tests:
pytest -v
Includes tests for:
- Core
Mapper ArrayMapperwildcard behavior- Optional mappings (
optional: true)
📄 License
MIT © 2025 Pedro Marques
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 jsonshift-0.8.2.tar.gz.
File metadata
- Download URL: jsonshift-0.8.2.tar.gz
- Upload date:
- Size: 9.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
18b53be977dad2818f0a4cee3694d8cb919ec0b1aa558bac70d7a17e74ba32ac
|
|
| MD5 |
78fd3f1193ce04afe3254ffbcb95fd26
|
|
| BLAKE2b-256 |
13eea3b44efebdcccc04c6b8ee51e6909b5d388f964e07f765f95de5d0619df2
|
File details
Details for the file jsonshift-0.8.2-py3-none-any.whl.
File metadata
- Download URL: jsonshift-0.8.2-py3-none-any.whl
- Upload date:
- Size: 8.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79a039883d48e8fbff464aaee667d1a24506482f7f8b213aa8f6e40360914108
|
|
| MD5 |
442cdb4cb9ab73bbc3735d9daffff9e9
|
|
| BLAKE2b-256 |
348fc8843698a546bd5d8f4edecf8a433da0445fc318ce48b16967abec487837
|