JSON Toolkit

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.

Intermedio~12 min de lecturaPublicado el 2026-10-30Actualizado el 2026-10-30
Portada de las preguntas frecuentes sobre consultas y validación

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 → 1 y 3, excluyendo el 2 anidado.

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 test de 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:

  1. La sintaxis de tuplas cambió: en draft-07 un items con valor de array expresaba una tupla; 2020-12 lo trasladó a prefixItems;
  2. $ref y 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:

  1. 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;
  2. Convertir el Pointer en lenguaje llano antes de mostrarlo al usuario final; /items/2/price es ú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 contrario format: "email" queda prácticamente inerte;
  • Algunos validadores tratan format como una aserción activada por defecto, así que el comportamiento es inconsistente;
  • Incluso activado, la regex de email es muy permisiva: a@b pasa.

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