Declarative JSON-to-JSON transformation engine with a built-in template language
Project description
Motor de transformación declarativa de JSON con lenguaje de plantillas integrado.
Instalación
pip install catnose
Uso rápido
> PYTHONPATH=src python3 -m catnose test/input.json test/transform.json
> PYTHONPATH=src python3 -m catnose test/input.json test/transform.json -o output.json
La entrada puede ser un array JSON o un objeto JSON. Si es un objeto, se trata
como una lista de un único elemento y recibe _rownum = 1.
input.json
[
{
"id": 1,
"first_name": "ana",
"last_name": "garcía",
"email": "ana.garcia@empresa.com",
"phone": "612-345-678",
"role": "admin",
"tags": ["ventas", "soporte", "formación"],
"bio": "Responsable del área de ventas y soporte técnico.",
"salary": 62000
},
{
"id": 2,
"first_name": "luis",
"last_name": "martínez",
"email": "luis.martinez@empresa.com",
"phone": "699 876 543",
"role": "user",
"tags": ["desarrollo", "backend"],
"bio": "Desarrollador backend con experiencia en Python.",
"salary": 38000
}
]
transform.json
{
"id": "id",
"email": "email",
"nombre": {
"_value": "{{ first_name.capitalize() }} {{ last_name.capitalize() }}",
"_comment": "Capitaliza nombre y apellido, que en la entrada vienen en minúsculas"
},
"rol_codigo": {
"_value": "{{ role.upper() }}",
"_comment": "Código de rol en mayúsculas para sistemas externos"
},
"rol_label": {
"_name": "role",
"_cases": {
"admin": "Administrador",
"user": "Usuario",
"manager": "Responsable",
"_default": "Otro"
},
"_comment": "Traduce el rol técnico a la etiqueta visible en la interfaz"
},
"telefono": {
"_value": "{{ re.sub(r'[\\s\\-.]', '', phone) }}",
"_comment": "Normaliza el número de teléfono quitando puntos, espacios o rayas separadoras"
},
"bio_normalizada": {
"_value": "{{ re.sub(r'\\s{2,}', ' ', bio).strip() }}",
"_comment": "Colapsa los espacios dobles o múltiples que pueda contener la bio"
},
"etiquetas": {
"_value": "{% for t in tags %}[{{ t }}]{% endfor %}",
"_comment": "Serializa la lista de etiquetas como cadena de tokens entre corchetes"
},
"nivel": {
"_value": "{% if salary >= 55000 %}Senior{% else %}{% if salary >= 45000 %}Mid{% else %}Junior{% endif %}{% endif %}",
"_comment": "Clasifica al empleado en Senior / Mid / Junior según su salario bruto anual"
},
"resumen": {
"_value": "{{ nombre }} · {{ rol_label }} · {{ nivel }}",
"_comment": "Descripción compacta que combina campos calculados anteriores"
}
}
Resultado
[
{
"id": 1,
"email": "ana.garcia@empresa.com",
"nombre": "Ana García",
"rol_codigo": "ADMIN",
"rol_label": "Administrador",
"telefono": "612345678",
"etiquetas": "[ventas][soporte][formación]",
"bio_normalizada": "Responsable del área de ventas y soporte técnico.",
"nivel": "Senior",
"resumen": "Ana García · Administrador · Senior"
},
{
"id": 2,
"email": "luis.martinez@empresa.com",
"nombre": "Luis Martínez",
"rol_codigo": "USER",
"rol_label": "Usuario",
"telefono": "699876543",
"etiquetas": "[desarrollo][backend]",
"bio_normalizada": "Desarrollador backend con experiencia en Python.",
"nivel": "Junior",
"resumen": "Luis Martínez · Usuario · Junior"
}
]
Formato del mapeador
El JSON transformado parte de un objeto vacío; sólo se incluyen los campos declarados explícitamente. El JSON original actúa como contexto de evaluación junto con los campos ya calculados (en orden de definición).
{
"<campo_destino>": "<campo_origen>",
"<campo_destino>": {
"_name": "<campo_origen>",
"_cases": {"v1": "nuevo_v1", "_default": "<por_defecto>"},
"_value": "cadena con {{ expr }}"
},
"<campo_lista>": {
"<sub_campo>": {
"_name": "<campo_origen>",
"_cases": {"v1": "nv1"},
"_value": "{{ expr }}"
}
}
}
| Clave | Descripción |
|---|---|
"<campo>": "<origen>" |
Shorthand — copia el valor de <origen> del JSON original |
_name |
Campo fuente en el JSON original (por defecto: la clave del mapper) |
_cases |
Tabla de sustitución de valores; _default se usa si no hay coincidencia |
_value |
Plantilla evaluada con el motor de expresiones |
_comment |
Comentario — ignorado por el motor, documenta la intención de la transformación |
Un mapping dict sin ninguna clave de control (_name, _cases, _value) se trata como lista de objetos, aplicando los sub-mapeos a cada elemento.
Motor de plantillas
Las expresiones _value usan la sintaxis {{ expr }} con evaluación Python completa. También se soportan bloques {% for %}, {% if %} / {% else %} / {% endif %}:
{
"summary": {"_value": "{{ title.upper() }} — {{ cwss_score }} puntos"},
"tags": {"_value": "{% for t in tags %}[{{ t }}]{% endfor %}"}
}
El contexto de evaluación incluye:
- Todos los campos del JSON original
- Los campos ya calculados por mappings anteriores (en orden)
_rownum(1..N, posición del elemento en el array; si la entrada es un objeto único, vale1)
Para campos de tipo lista, el contexto incluye además los campos de cada elemento de la lista.
Historial de cambios
1.2.0
- Soporte para
_commenten los mappings del JSON de transformación: se puede añadir"_comment": "..."junto a_value,_nameo_casespara documentar la intención de cada campo. El motor lo ignora y no produce ningún campo en el resultado.
1.1.0
transformacepta ahora un objeto JSON (dict) además de un array. Si la entrada es un objeto, devuelve un objeto en lugar de una lista.- CLI actualizado para admitir ficheros de entrada que sean un objeto JSON único, asignándole
_rownum = 1.
1.0.1
- Añadido logotipo del proyecto.
1.0.0
rownumrenombrado a_rownumen el contexto de evaluación para evitar colisiones con campos del JSON original._parse_listeliminada del API pública (era un detalle de implementación interno).
0.0.1
- Versión inicial: motor de transformación declarativa, motor de plantillas
{{ expr }}/{% for %}/{% if %}, y CLI.
Licencia
MIT
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
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 catnose-1.2.0.tar.gz.
File metadata
- Download URL: catnose-1.2.0.tar.gz
- Upload date:
- Size: 257.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83ce424c3e79e8e8e4259998b814d2da3ac18a229c35b4b9232960803594410e
|
|
| MD5 |
e457cfe7abaf8e6b83c09076a6fae1bc
|
|
| BLAKE2b-256 |
1986831ab00778cc1dd5b06dbce67354c75b38766e8795b75bede8de1005d628
|
File details
Details for the file catnose-1.2.0-py3-none-any.whl.
File metadata
- Download URL: catnose-1.2.0-py3-none-any.whl
- Upload date:
- Size: 9.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f956e775716d311098913424845e768e86778e84805f259d94563f30521a5852
|
|
| MD5 |
1793ce584f09918faa6c18234fb3d07f
|
|
| BLAKE2b-256 |
a8be6966dda23b1f274505c0b8bfe52b7a1059fc729ec0e21e613abf8f42d405
|