Skip to main content

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 receives None (not replaced).
  • defaults only fills values when the destination field is absent (does not overwrite None or 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 MappingMissingError is 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 (unless optional: 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
  • ArrayMapper wildcard behavior
  • Optional mappings (optional: true)

📄 License

MIT © 2025 Pedro Marques

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

jsonshift-0.8.2.tar.gz (9.7 kB view details)

Uploaded Source

Built Distribution

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

jsonshift-0.8.2-py3-none-any.whl (8.3 kB view details)

Uploaded Python 3

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

Hashes for jsonshift-0.8.2.tar.gz
Algorithm Hash digest
SHA256 18b53be977dad2818f0a4cee3694d8cb919ec0b1aa558bac70d7a17e74ba32ac
MD5 78fd3f1193ce04afe3254ffbcb95fd26
BLAKE2b-256 13eea3b44efebdcccc04c6b8ee51e6909b5d388f964e07f765f95de5d0619df2

See more details on using hashes here.

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

Hashes for jsonshift-0.8.2-py3-none-any.whl
Algorithm Hash digest
SHA256 79a039883d48e8fbff464aaee667d1a24506482f7f8b213aa8f6e40360914108
MD5 442cdb4cb9ab73bbc3735d9daffff9e9
BLAKE2b-256 348fc8843698a546bd5d8f4edecf8a433da0445fc318ce48b16967abec487837

See more details on using hashes here.

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