---
id: 61f2009278a5f7366f79395da59447cf
author: lemonmint
title: Análisis de los cambios de comportamiento de encoding/json/v2 en Go 1.25 y de la capa de compatibilidad con v1
description: Compara la coincidencia sensible a mayúsculas y minúsculas, los cambios semánticos de omitempy/omitzero, la serialización de slices y mapas nil, y las opciones de encoding/json/v2, introducido como función experimental de Go 1.25 (goexperiment.jsonv2).
language: es
date: 2025-06-20T05:38:20.100520237Z
path: /blog/posts/go-1-25-encoding-json-v1-vs-v2-z74a36b08
category: release
---

El paquete `encoding/json` v2 de Go es una nueva implementación destinada a mejorar varias deficiencias de la v1 (falta de consistencia, comportamientos sorprendentes y problemas de rendimiento). Se activa mediante la etiqueta de compilación `goexperiment.jsonv2`, lo que la convierte en una característica experimental.

Lo más importante es que **cuando v2 está activado, v1 funciona como una capa de compatibilidad que emula el comportamiento de v1 sobre la implementación de v2**. Esto se logra mediante la función `DefaultOptionsV1()` del archivo `v2_v1.go`. Es decir, v2 ofrece opciones que permiten reproducir por completo el comportamiento de v1, al mismo tiempo que presenta un nuevo comportamiento predeterminado más estricto y predecible.

Los objetivos principales de v2 son los siguientes:

1. **Mejora de la corrección y la previsibilidad**: aplica por defecto reglas más estrictas (por ejemplo, distinción entre mayúsculas y minúsculas y prohibición de claves duplicadas) para reducir los comportamientos inesperados.
2. **Mejora del rendimiento**: se rediseñaron los motores de análisis y codificación para aumentar la eficiencia.
3. **Mayor flexibilidad y control**: se introduce un sistema detallado de opciones que permite al desarrollador controlar con precisión el procesamiento de JSON.

---

## Principales diferencias de semántica y comportamiento

Se resumen por elementos las diferencias de comportamiento entre v1 y v2, tomando como referencia el archivo `v2_test.go`.

### 1. Coincidencia de nombres de campo (distinción entre mayúsculas y minúsculas)

- **Comportamiento de v1**: al deserializar (unmarshal) los miembros de un objeto JSON en los campos de una estructura de Go, la coincidencia **ignora las mayúsculas y minúsculas (case-insensitive)**. Tanto `"FirstName"` como `"firstname"` se asignan al campo `FirstName`.
- **Comportamiento de v2**: por defecto **distingue entre mayúsculas y minúsculas (case-sensitive)** y solo asigna los campos que coinciden exactamente.
- **Motivo del cambio**: la coincidencia sin distinción de mayúsculas puede ser causa de comportamientos inesperados y provoca una degradación del rendimiento al procesar campos que no coinciden. v2 adopta como comportamiento predeterminado uno más claro y predecible.
- **Opciones relacionadas**: en v2 se puede activar explícitamente la coincidencia sin distinción de mayúsculas para un campo concreto mediante la opción de etiqueta `json:"...,case:ignore"`, o aplicarla de forma global con `json.MatchCaseInsensitiveNames(true)`.

### 2. Cambio de significado de la opción de etiqueta `omitempty`

- **Comportamiento de v1**: omite el campo según el "estado vacío" del valor de Go. En este caso, el "estado vacío" significa `false`, `0`, punteros o interfaces `nil`, y arreglos, slices, mapas o cadenas de longitud 0.
- **Comportamiento de v2**: omite el campo según el "estado vacío" del valor JSON codificado. Es decir, se omite cuando se codifica como `null`, `""`, `{}` o `[]`.
- **Motivo del cambio**: la definición de v1 depende del sistema de tipos de Go. v2 se basa en el sistema de tipos de JSON, lo que proporciona un comportamiento más consistente. Por ejemplo, en v1 se omite el valor `false` de un tipo `bool`, mientras que en v2 `false` es un valor JSON no vacío y, por tanto, no se omite. En v2 se añade la opción `omitzero`, que puede reemplazar el comportamiento de `omitempty` de v1 aplicado a `0` o `false`.
- **Opciones relacionadas**: para obtener en v2 el mismo comportamiento que en v1, se usa la opción `json.OmitEmptyWithLegacyDefinition(true)`.

### 3. Cambio de comportamiento de la opción de etiqueta `string`

- **Comportamiento de v1**: se aplica a campos de tipo numérico, booleano y cadena. Codifica de nuevo el valor dentro de una cadena JSON (por ejemplo, `int(42)` -> `"42"`). No se aplica de forma recursiva a los valores dentro de tipos compuestos (slices, mapas, etc.).
- **Comportamiento de v2**: se aplica **únicamente a tipos numéricos** y lo hace de forma recursiva. Es decir, los números dentro de un slice como `[]int` también se codifican como cadenas JSON.
- **Motivo del cambio**: el uso principal de la opción `string` es representar enteros de 64 bits como cadenas para evitar la pérdida de precisión. El comportamiento de v1 era limitado e inconsistente. v2 se centra en este uso principal y amplía el comportamiento de forma recursiva para hacerlo más útil.
- **Opciones relacionadas**: la opción `json.StringifyWithLegacySemantics(true)` permite imitar el comportamiento de v1.

### 4. Serialización (marshal) de slices y mapas `nil`

- **Comportamiento de v1**: los slices `nil` y los mapas `nil` se serializan como `null`.
- **Comportamiento de v2**: por defecto, los slices `nil` se serializan como `[]` (arreglo vacío) y los mapas `nil` como `{}` (objeto vacío).
- **Motivo del cambio**: `nil` es un detalle de implementación del lenguaje Go, y no resulta conveniente exponerlo en un formato JSON independiente del lenguaje. `[]` y `{}` son representaciones más habituales de colecciones vacías.
- **Opciones relacionadas**: en v2, las opciones `json.FormatNilSliceAsNull(true)` o `json.FormatNilMapAsNull(true)` permiten serializar como `null`, igual que en v1.

### 5. Deserialización (unmarshal) de arreglos

- **Comportamiento de v1**: al deserializar en un arreglo de Go (`[N]T`), no se produce error aunque la longitud del arreglo JSON sea distinta de la del arreglo de Go. Si el arreglo JSON es más corto, el espacio sobrante se rellena con valores cero; si es más largo, el excedente se descarta.
- **Comportamiento de v2**: la longitud del arreglo JSON **debe coincidir exactamente** con la del arreglo de Go. En caso contrario, se produce un error.
- **Motivo del cambio**: en Go, la longitud de los arreglos de tamaño fijo suele tener un significado importante. El comportamiento de v1 puede provocar pérdidas silenciosas de datos. v2 aplica reglas más estrictas para mejorar la corrección.
- **Opciones relacionadas**: la opción `json.UnmarshalArrayFromAnyLength(true)` permite imitar el comportamiento de v1.

### 6. Tratamiento de `time.Duration`

- **Comportamiento de v1**: `time.Duration` se trata internamente como `int64` y se codifica como un número JSON en nanosegundos.
- **Comportamiento de v2**: se codifica como una cadena JSON con un formato como `"1h2m3s"`, mediante el método `time.Duration.String()`.
- **Motivo del cambio**: los nanosegundos numéricos son poco legibles, y la representación textual estándar de `time.Duration` resulta más útil.
- **Opciones relacionadas**: se puede usar el comportamiento de v1 mediante la opción de etiqueta `json:",format:nano"` o la opción `json.FormatTimeWithLegacySemantics(true)`.

### 7. Tratamiento de UTF-8 no válido

- **Comportamiento de v1**: durante la serialización o deserialización, si una cadena contiene bytes UTF-8 no válidos, estos se reemplazan **silenciosamente** por el carácter de reemplazo Unicode (`\uFFFD`).
- **Comportamiento de v2**: por defecto, al encontrar UTF-8 no válido **devuelve un error**.
- **Motivo del cambio**: evitar la corrupción silenciosa de datos y cumplir con el estándar JSON más estricto (RFC 7493).
- **Opciones relacionadas**: la opción `jsontext.AllowInvalidUTF8(true)` permite imitar el comportamiento de v1.

### 8. Tratamiento de nombres de miembros duplicados en objetos

- **Comportamiento de v1**: permite que aparezcan miembros con el mismo nombre dentro de un objeto JSON. Prevalece el último valor.
- **Comportamiento de v2**: por defecto, si existen nombres de miembros duplicados, **devuelve un error**.
- **Motivo del cambio**: el estándar RFC 8259 no define el comportamiento para nombres duplicados, por lo que las implementaciones pueden comportarse de forma distinta. Esto puede ser una causa de vulnerabilidades de seguridad. v2 rechaza explícitamente estos casos para mejorar la corrección y la seguridad.
- **Opciones relacionadas**: la opción `jsontext.AllowDuplicateNames(true)` permite imitar el comportamiento de v1.

---

## Diferencias de implementación y arquitectura

- **v1**: depende en gran medida de `decodeState` en `decode.go` y de la máquina de estados escrita manualmente en `scanner.go`. Se trata de una estructura monolítica en la que el análisis sintáctico y el análisis semántico están fuertemente acoplados.
- **v2**: la arquitectura es más modular.
  - **`encoding/json/jsontext`**: proporciona un tokenizador `Decoder` y un codificador `Encoder` de JSON de bajo nivel y alto rendimiento. Este paquete se centra únicamente en los aspectos sintácticos de JSON.
  - **`encoding/json/v2`**: se basa en `jsontext` para gestionar la conversión semántica entre los tipos de Go y los valores JSON.
  - Gracias a esta separación, el análisis sintáctico y el semántico quedan desacoplados, lo que mejora la claridad del código y el rendimiento.

---

## Nuevas API y funciones de v2

v2 ofrece un control muy flexible mediante el sistema `json.Options`.

- **`json.Options`**: conjunto de opciones que modifican el comportamiento de la serialización y deserialización.
- **`json.JoinOptions(...)`**: combina varias opciones en una sola.
- **`WithMarshalers` / `WithUnmarshalers`**: función potente que permite inyectar la lógica de serialización y deserialización para tipos concretos sin implementar las interfaces `Marshaler` o `Unmarshaler`. Resulta especialmente útil al tratar tipos de paquetes externos.
- **Nuevas opciones**: `RejectUnknownMembers`, `Deterministic(false)`, `FormatNilSliceAsNull`, entre otras, permiten controlar comportamientos que en v1 no eran posibles.

---

## Conclusión

`encoding/json` v2 es una implementación moderna que, a partir de la experiencia con v1, mejora notablemente la **corrección, el rendimiento y la flexibilidad**. Su comportamiento predeterminado es más estricto, pero gracias a un sistema de `Options` detallado es posible reproducir por completo el comportamiento de v1, lo que permite mantener la compatibilidad con el código existente e incorporar gradualmente las ventajas de v2.

- En **proyectos nuevos**, se recomienda usar v2 como opción predeterminada.
- En **proyectos existentes**, es posible seguir usando `jsonv1` tal cual, o migrar a `jsonv2` e incorporar gradualmente el comportamiento más estricto de v2 mediante `DefaultOptionsV1()`.
