FAQ de consultas y validación: JSONPath y Schema
En resumen: Si las consultas de ruta deben usar JSONPath o Pointer, cómo elegir entre Patch y Merge Patch, qué puede impedir Schema y cómo escoger una versión de draft: todo explicado de una vez.

En resumen: usa JSONPath para encontrar nodos por condición, JSON Pointer o JSON Patch para direccionar y editar ubicaciones exactas, y Merge Patch para fusionar configuración; fija los Schemas nuevos en 2020-12 y activa allErrors para que todos los fallos vuelvan en una sola pasada.
Consultar es «encontrarlo»; validar es «confirmar que es correcto». Cada uno tiene su propio estándar, así que no los mezcles.
1. Consultas de ruta
¿Cómo elijo entre JSONPath y JSON Pointer?
Depende de si estás «buscando por condición» o «obteniendo por posición».
| Dimensión | JSONPath | JSON Pointer |
|---|---|---|
| Estándar | RFC 9535 (2024) | RFC 6901 (2013) |
| Expresión | $.users[?(@.age > 18)].id |
/users/0/id |
| Comodines y recursión | Admite *, .. |
No admitidos |
| Filtrado condicional | Admitido | No admitido |
| Resultado | Un conjunto de nodos (posiblemente varios) | Una única ubicación |
| Expresividad | Alta, pero con un pesado legado de diferencias entre implementaciones | Baja, pero totalmente determinista |
| Uso típico | Extracción de logs, depuración, exploración de datos | JSON Patch, direccionamiento de configuración, rutas de error |
La regla de elección es directa:
- Necesitas «obtener todos los nodos que cumplen una condición» → JSONPath;
- Necesitas «fijar una ubicación exacta y añadir, eliminar o modificar algo en ella» → JSON Pointer;
- Úsalos juntos: encuentra el nodo con JSONPath y describe su ubicación con Pointer.
No uses JSONPath para direccionar en un contrato de API. JSONPath devuelve un conjunto, y las tres ramas para resultados vacíos, de un solo elemento y de varios elementos complican el protocolo; Pointer significa «o apunta a una ubicación existente o no existe», lo que encaja mejor en un contrato.
¿Cuál es la diferencia entre $.. y $.*?
$.. es descenso recursivo y recorre todos los niveles; $.* devuelve solo los hijos directos del nivel actual.
{
"a": { "id": 1, "child": { "id": 2 } },
"b": { "id": 3 }
}
$.*→ los dos objetos{id:1, child:{...}}y{id:3};$..id→ tres valores:1,2,3;$.*.id→1y3, excluyendo el2anidado.
El descenso recursivo es útil pero caro: $.. recorre todo el árbol, lo que es O(n) en un documento grande, y es fácil que recoja campos profundos que no querías.
Si un nivel concreto sirve, evita ...
¿Pueden los filtros comparar cadenas? ¿Pueden coincidir con regex?
La comparación de cadenas funciona; la regex depende de la implementación.
Sintaxis estándar (RFC 9535):
$.items[?(@.status == "active")]
$.items[?(@.price > 10 && @.stock > 0)]
$.items[?(@.name == $.target)] // comparar con otro nodo
La comparación de cadenas usa comillas dobles (en RFC 9535 también se admiten comillas simples, pero algunas implementaciones antiguas aceptan solo comillas dobles).
La comparación numérica sigue la semántica numérica; mezclar una cadena con un número, como "10" > 9, es comportamiento indefinido en el estándar, así que no confíes en ello.
Para regex, RFC 9535 define las funciones match() y search(), pero su soporte varía bastante entre implementaciones.
El enfoque seguro: deja que JSONPath haga un filtrado grueso, y haz la coincidencia regex en el código después de obtener los resultados.
¿Debo usar JSON Patch o Merge Patch?
Ambos describen «cómo cambiar algo», pero su expresividad y semántica son completamente distintas.
| Dimensión | JSON Patch (RFC 6902) | Merge Patch (RFC 7386) |
|---|---|---|
| Forma | Un array de operaciones [{"op":"add",...}] |
Un objeto parcial |
| Operaciones admitidas | add / remove / replace / move / copy / test | Solo fusión (borrado mediante null) |
| Puede direccionar elementos de array | Sí, por índice | No, se reemplaza el array completo |
| Atomicidad | test permite bloqueo optimista |
Ninguna |
| Tamaño del documento | Grande pero preciso | Pequeño pero limitado en expresividad |
| Adecuado para | Ediciones de grano fino, actualizaciones parciales de API | Fusión de configuración, envío parcial de formularios |
Criterios de decisión:
- Cambiar un campo del tercer elemento de un array → JSON Patch;
- Fusionar varias entradas de configuración en la configuración existente → Merge Patch;
- Obtener control de concurrencia del tipo «falla si el valor original no es X» → el
testde JSON Patch.
Ojo con la trampa de la semántica de null en Merge Patch: null significa «eliminar esta clave», no «poner el valor a null».
Poner un valor a null es imposible con Merge Patch.
2. Validación con Schema
¿Qué problemas resuelve JSON Schema?
Convierte «qué aspecto deberían tener los datos» de un acuerdo verbal en un contrato comprobable por máquina. Los errores que realmente puede impedir:
- Campos obligatorios que faltan (
required); - Tipo incorrecto, por ejemplo una cadena donde se espera un número (
type); - Valores fuera de rango (
minimum/maximum/minLength); - Valores fuera del conjunto permitido (
enum); - Tipos de elementos de array inconsistentes o longitud de array incorrecta (
items/minItems); - Campos que faltan en estructuras anidadas (validación recursiva mediante
properties); - Campos extra (
additionalProperties: false).
Lo que no puede resolver: reglas de negocio («la hora de fin debe ser posterior a la de inicio»), condiciones entre campos (algunas pueden expresarse con if/then,
pero la legibilidad se resiente), unicidad en la base de datos y comprobaciones de permisos.
Así que Schema es el portero de la «forma de los datos», no un sustituto de la lógica de negocio.
¿Debo usar draft-07 o 2020-12?
Usa 2020-12 para proyectos nuevos; para sistemas existentes, comprueba qué admite el validador.
| Versión | Estado | Cambios clave |
|---|---|---|
| draft-04 | Obsoleta | Muchas bibliotecas antiguas aún la usan por defecto |
| draft-07 | Estándar de facto | El soporte más amplio, el ecosistema más rico |
| 2019-09 | Transitoria | Introdujo $defs, unevaluatedProperties |
| 2020-12 | Recomendada actualmente | items se divide en prefixItems + items, admite $dynamicRef |
Dos trampas de migración:
- La sintaxis de tuplas cambió: en draft-07 un
itemscon valor de array expresaba una tupla; 2020-12 lo trasladó aprefixItems; $refy palabras clave hermanas: en draft-07 las palabras clave hermanas se ignoran cuando está presente$ref; 2020-12 permite que coexistan.
Si el validador solo admite draft-07, escribir sintaxis de 2020-12 se ignora silenciosamente — y el caso más peligroso es que «la validación pase» cuando en realidad no se validó nada.
¿Cómo genero tipos de TypeScript a partir de un Schema?
Dos rutas: derivar tipos del Schema, o generar el Schema a partir de tipos.
De Schema a tipos (el backend define el contrato, el frontend lo consume):
import type { FromSchema } from 'json-schema-to-ts';
const userSchema = {
type: 'object',
properties: {
id: { type: 'string' },
age: { type: 'integer' },
},
required: ['id'],
additionalProperties: false,
} as const;
type User = FromSchema<typeof userSchema>;
// { id: string; age?: number }
La clave es as const: sin ella, type: 'string' se infiere como string en lugar del literal 'string', y la inferencia se rompe.
De tipos a Schema (el frontend ya tiene tipos y necesita validación en tiempo de ejecución):
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
const userSchema = z.object({ id: z.string(), age: z.number().int().optional() });
const jsonSchema = zodToJsonSchema(userSchema);
La ventaja de esta ruta es que «una definición produce a la vez tipos y validación en tiempo de ejecución»; el coste es introducir un DSL,
y el Schema generado puede arrastrar detalles de implementación (por ejemplo una estructura $ref poco atractiva).
¿Cómo localizo el campo concreto cuando falla la validación?
Mira el instancePath (2020-12) o dataPath (draft-07) que devuelve el validador; es un JSON Pointer a la ubicación del error.
[
{
"instancePath": "/items/2/price",
"schemaPath": "#/properties/items/items/properties/price/type",
"keyword": "type",
"message": "must be number"
}
]
instancePath: "/items/2/price" te dice directamente: el campo price del tercer elemento del array items tiene el tipo incorrecto.
Dos sugerencias prácticas:
- Devolver todos los errores a la vez en lugar de lanzar en el primero: que el frontend corrija todo en una pasada es más rápido que intentos de ida y vuelta;
- Convertir el Pointer en lenguaje llano antes de mostrarlo al usuario final;
/items/2/pricees útil para desarrolladores e inútil para usuarios.
allErrors: true es el interruptor en la mayoría de validadores (Ajv exige activarlo explícitamente); el valor predeterminado suele ser el modo fail-fast.
¿Debe activarse additionalProperties: false?
Actívalo para contratos internos; sé prudente con las APIs públicas.
Una vez activado, cualquier campo que no esté declarado bajo properties falla la validación. El beneficio es detectar errores tipográficos
(userNmae) y traspasos inesperados; la desventaja es que un campo añadido aguas arriba rompe a los consumidores aguas abajo, destruyendo la compatibilidad hacia atrás.
Compromiso:
| Escenario | Recomendación |
|---|---|
| Entre servicios internos | Activar. Los cambios de campo pasan por gestión de versiones |
| APIs públicas externas | Desactivar. Permite que aguas arriba añada campos |
| Consumir datos de terceros | Desactivar. No controlas el otro lado |
| Validación de configuración | Activar. Un error tipográfico en la configuración debe fallar de inmediato |
¿Cómo valido que los elementos de un array son únicos?
Dos semánticas distintas — no las confundas:
{ "uniqueItems": true }
Esto deduplica el elemento completo, exigiendo que el array no contenga dos elementos profundamente iguales.
{
"type": "array",
"items": { "type": "object", "properties": { "id": { "type": "string" } } },
"uniqueItems": true
}
El fragmento anterior solo garantiza que los objetos en su conjunto no estén duplicados; {"id":"a","name":"x"} y {"id":"a","name":"y"} no cuentan como duplicados.
Para deduplicar por un campo específico, Schema no puede expresarlo; tienes que validarlo en la capa de negocio:
const ids = items.map((item) => item.id);
if (new Set(ids).size !== ids.length) {
throw new Error('id 重复');
}
¿format valida realmente correos y fechas?
No necesariamente. En la especificación, format es por defecto una «anotación» en lugar de una «aserción», así que un validador puede recogerlo sin validarlo.
- Ajv exige instalar y configurar el paquete aparte
ajv-formats, de lo contrarioformat: "email"queda prácticamente inerte; - Algunos validadores tratan
formatcomo una aserción activada por defecto, así que el comportamiento es inconsistente; - Incluso activado, la regex de
emailes muy permisiva:a@bpasa.
Conclusión: no trates format como validación fuerte. Haz la validación estricta de correos, URLs y fechas en tu código de negocio;
mantener format en el Schema como documentación vale la pena, pero no esperes que frene datos sucios.
3. Referencia rápida
| Pregunta | Conclusión |
|---|---|
| Encontrar nodos por condición | JSONPath |
| Direccionamiento preciso, aplicar parches | JSON Pointer |
| Cambiar elementos de un array | JSON Patch, Merge Patch no puede |
| Fusionar configuración | Merge Patch |
| Versión de Schema para proyectos nuevos | 2020-12, ojo con prefixItems |
| Generar tipos TS a partir de un Schema | json-schema-to-ts + as const |
| Localizar errores de validación | Leer instancePath, activar allErrors |
| Deduplicar por campo | Schema no puede; usa un Set en la capa de negocio |
format: email |
La mayoría de validadores no lo comprueban por defecto, así que no confíes en él |