Skip to main content

A deterministic JSON payload mapper that transforms source data into target structures using dotted paths, indices and wildcards.

Project description

✨ jsonshift

A lightweight Python package to convert one JSON payload into another using a declarative mapping spec defined in JSON.

Designed for deterministic system integrations, data pipelines, and API adapters.


⚙️ Engine rules

  • If the source path does not exist → raises MappingMissingError (unless optional: true is set)

  • If the source value is null / None → the destination receives None (defaults do NOT override None)

  • defaults only fill values when the destination field is absent (never overwrite existing values or None)

  • Supports:

    • dotted paths
    • indexed paths ([0])
    • wildcard paths ([*])
    • automatic list creation
    • infinite nesting depth
  • Supports optional mappings using optional: true

  • Supports dynamic defaults:

    • { "$now": "date" }
    • { "$now": "datetime" }
    • { "$now": "time" }
    • { "$concat": [...] }
    • { "$upper": ... }
    • { "$lower": ... }
    • { "$format": { "template": "...", "args": {...} } }

🧩 Installation

pip install jsonshift
# or for development:
pip install -e .[dev]

🚀 Complex example (Python)

from jsonshift import Mapper

payload = {
    "customer_name": "John Doe",
    "cpf": "12345678901",
    "email": "john@doe.com",
    "amount": 1500.0,
    "installments": 6,
    "products": [
        {"id": "P-001", "name": "Notebook", "price": 4500.0},
        {"id": "P-002", "name": "Mouse", "price": 250.0}
    ]
}

spec = {
    "map": {
        "customer.name": "customer_name",
        "customer.cpf": "cpf",
        "customer.email": {
            "path": "email",
            "optional": True
        },

        "contract.amount": "amount",
        "contract.installments": "installments",

        "contract.products[*].code": "products[*].id",
        "contract.products[*].title": "products[*].name",
        "contract.products[*].price": "products[*].price",

        "contract.main_product[0].code": "products[0].id",
        "contract.main_product[0].title": "products[0].name"
    },

    "defaults": {
        "contract.type": "CCB",
        "contract.origin": "ORQ",

        "contract.created_date": {"$now": "date"},
        "contract.created_at": {"$now": "datetime"},
        "contract.created_time": {"$now": "time"},

        "contract.products[*].currency": "BRL"
    }
}

out = Mapper().transform(spec, payload)
print(out)

Output (example):

{
  "customer": {
    "name": "John Doe",
    "cpf": "12345678901",
    "email": "john@doe.com"
  },
  "contract": {
    "amount": 1500.0,
    "installments": 6,
    "type": "CCB",
    "origin": "ORQ",
    "created_date": "2025-01-08",
    "created_at": "2025-01-08T12:00:00",
    "created_time": "12:00:00",
    "products": [
      {
        "code": "P-001",
        "title": "Notebook",
        "price": 4500.0,
        "currency": "BRL"
      },
      {
        "code": "P-002",
        "title": "Mouse",
        "price": 250.0,
        "currency": "BRL"
      }
    ],
    "main_product": [
      {
        "code": "P-001",
        "title": "Notebook"
      }
    ]
  }
}

🧠 Dynamic string defaults

In addition to static values and $now, jsonshift supports dynamic string functions inside defaults.

These functions allow composing and transforming values from literals and payload fields in a deterministic and explicit way.


🔹 $concat

Concatenates string literals and payload values.

{
  "defaults": {
    "user.code": {
      "$concat": [
        "USR-",
        { "$path": "id" }
      ]
    }
  }
}

Result:

{
  "user": {
    "code": "USR-123"
  }
}

🔹 $upper / $lower

Transforms strings to upper or lower case.

{
  "defaults": {
    "name_upper": {
      "$upper": { "$path": "name" }
    },
    "email_lower": {
      "$lower": { "$path": "email" }
    }
  }
}

🔹 $format

Formats strings using Python-style templates.

{
  "defaults": {
    "external_id": {
      "$format": {
        "template": "{id}-{cpf}",
        "args": {
          "id": { "$path": "id" },
          "cpf": { "$path": "cpf" }
        }
      }
    }
  }
}

📌 Notes

  • Dynamic functions are evaluated only inside defaults
  • Paths must be explicitly declared using { "$path": "..." }
  • Missing paths raise MappingMissingError
  • Dynamic defaults never override existing values or None

🖥️ Command-line interface (CLI)

Using the same example from examples/:

jsonshift --spec examples/spec.json --input examples/payload.json

Or via stdin:

cat examples/payload.json | jsonshift --spec examples/spec.json

The output structure is identical to the Python example above.


🧪 Testing

pytest -v

📄 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-1.2.0.tar.gz (9.8 kB view details)

Uploaded Source

Built Distribution

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

jsonshift-1.2.0-py3-none-any.whl (7.3 kB view details)

Uploaded Python 3

File details

Details for the file jsonshift-1.2.0.tar.gz.

File metadata

  • Download URL: jsonshift-1.2.0.tar.gz
  • Upload date:
  • Size: 9.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for jsonshift-1.2.0.tar.gz
Algorithm Hash digest
SHA256 6c9e4b8c3fc504e90f17e0d8537594345ab440cb27788fe0018dffaf7a7e9582
MD5 92e1edad111097f8401926f812629cc9
BLAKE2b-256 9214e0b330f6c0066e15cbce5000e8ea0c1e0f2b21d7e56aba5635522280b9b2

See more details on using hashes here.

File details

Details for the file jsonshift-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: jsonshift-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 7.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-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 580ba4544de0022fc822f54eb277446a1adbf1430489f6e57ba679f63713b7f8
MD5 a76e72e12b6d92353ff52736ac00a7c9
BLAKE2b-256 e10493dd7408bd162fd75330296914d5d74e599d35666b05de351fce0c74f9cf

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