A Go JSON v2 migration can change what your program accepts and what it sends. Start with that contract before comparing speed. Our original local exercise runs eight correctness fixtures and records 120 repeated benchmark observations on Go 1.27.3. All 16 expected outcomes matched, yet seven selected fixtures produced different outputs or error states between the two APIs.
That is a teaching result, not an estimate that 87.5% of applications will break. We deliberately chose edge cases that expose differences. The useful question is whether those differences matter to your consumers, stored data and error handling.
Research checked: 7 November 2026. The fresh news is the Go team’s 4 November introduction to the stable packages. Our local measurements are separate from the historical benchmark repository linked by that announcement.
What changed, and what did not
The official Go announcement introduces the stable encoding/json/v2 and encoding/json/jsontext packages available in Go 1.27. It describes broadly comparable encoding performance and decoding improvements reaching tenfold in benchmarks. Those are qualified upstream statements, rather than a promise about your application.
The original encoding/json package remains supported. Therefore, you can retain it while investigating compatibility. A successful compile after changing an import does not establish that external consumers still receive the representation they expect.
This guide addresses a specific migration decision. If you are comparing language choices more broadly, use our programming languages coverage. Here, the acceptance criteria are concrete JSON results, explicit errors and a documented workload.
Go JSON v2 migration: eight fixtures before a stopwatch
We wrote one small, standard-library-only Go program. It calls both APIs on the same runtime with default options. Four cases decode input; four encode values. Expected results were set before execution using the official reference, then compared with the observed output or an error marker.

The ordinary object produces the same value. In contrast, the duplicate-name and invalid-UTF-8 cases return errors under v2 defaults. Our v1 fixture keeps the later duplicate value and replaces the invalid byte. Those observations apply to these inputs; they do not certify arbitrary documents.
The uppercase NAME input also deserves attention. It populates the tagged name field under v1, while our v2 result leaves the field empty. No error does not necessarily mean the intended field was populated. Tests should compare the resulting value, rather than checking only whether the call returned nil.
Encoding fixtures change representation
For encoding, our nil slice changes from null to an empty array. The nil map changes from null to an empty object. Meanwhile, a zero integer with omitempty is omitted by v1 but retained by v2 in this fixture. The one-second duration encodes as a number under v1 and returns an error with v2 defaults.
The official migration guide explains compatibility options and gradual migration. Choose a representation deliberately. In particular, retaining v1 behavior can be a valid decision when a consumer relies on it; adopting stricter defaults can also be intentional after checking downstream effects.
Keep the benchmark’s date attached to its numbers
The announcement links to the go-json-experiment/jsonbench repository. Its README identifies Go 1.23.5 and experimental packages pinned to 27 January 2025. It describes six datasets and an AMD Ryzen 9 9950X.
That provenance matters. Those historical results are useful background, but they are not our Go 1.27.3 measurements. We have not reproduced that repository’s workload, and we do not relabel its ratios as a new stable-release comparison.
Likewise, comparing a permissive decoder with one that rejects an input can compare different work. First decide what counts as correct. Then time the accepted workload and preserve any errors alongside the timings.
Our local test: one narrow payload family
Our benchmark uses a synthetic JSON object containing one ASCII string named name. Its three exact encoded sizes are 1,024, 102,400 and 1,048,576 bytes. These correspond to 1 KiB, 100 KiB and 1 MiB; they are not a sample of production API traffic.
Tech Consulting Tailored to Your Coding Journey
Get expert guidance in coding with a personalized consultation. Receive unique, actionable insights delivered in 1-3 business days.
Get StartedWe measure encoding and decoding separately. Each is tested with a concrete struct and an interface-based value. Both APIs run on the same Go 1.27.3 toolchain, macOS 27.0.1, Apple M5, darwin/arm64. No external module or custom compatibility option is used.
For each combination, the program records five timed batches. A batch contains 200 operations for the smallest payload, 30 for the middle payload and ten for the largest. The charts show the median batch mean, with the observed minimum and maximum. These ranges are not confidence intervals or individual-request latency percentiles.
We warm the calls before timing, check encoded bytes against the input fixture and record operation errors. All timed combinations reported zero errors. Allocation counts come from testing.AllocsPerRun with five measured calls after its warm-up, separately from timing.
Decoding results: struct and interface

In concrete decoding at 1 MiB, the median batch mean was 603.40 microseconds for v1 and 315.85 for v2. Their ratio is approximately 1.91 in this test. It is neither a tenfold result nor a forecast for nested objects, custom methods or network services.

The interface destination is a separate comparison. Its largest-payload medians were 607.50 and 314.41 microseconds. Similar numbers here do not justify dropping destination types from future reports. More complex values can exercise different paths.
Encoding and allocations

Encoding tells a different story. At 1 MiB, its concrete medians were 404.35 and 405.57 microseconds. We treat that as approximately equal in this limited exercise, rather than declaring a winner from a small difference. This is why a decoding headline cannot describe every operation.

Allocation counts add another dimension, but they do not measure peak memory or total allocated bytes. Consequently, use them alongside error behavior and time. If your service’s bottleneck is elsewhere, faster JSON processing may barely change its end-to-end performance.
Choose a migration decision you can explain
A Go JSON v2 migration needs two gates: acceptable behavior, then acceptable performance. Our non-chart decision tree keeps those gates separate and offers three routes: proceed, accept a documented behavior change, or use explicit compatibility and defer the change.

For example, a fictional catalogue client may distinguish an unknown list represented by null from a known empty list. Changing that output without agreement could alter its behavior. By contrast, a new service may deliberately choose the empty-array contract. Neither decision follows from a benchmark alone.
Use the blank decision log to record your intended contract, chosen option, owner and verification. Avoid using “tests passed” as a substitute for naming the cases. A passing exercise with eight fixtures is evidence about eight fixtures.
Download the results and reproduce the exercise
- Eight-fixture expected and observed results (CSV).
- All 120 local benchmark observations (CSV).
- Blank application migration decision log (CSV).
- Runtime, workload and reproduction manifest (CSV).
The accompanying reproducible code pack (ZIP) contains the original program, those CSVs and a JSON manifest. With Go 1.27.3, run GO111MODULE=off go run scripts/migration.go from the extracted folder. Keep the data directory present. Rerunning writes new measurements, so preserve the supplied baseline first.
For a reviewable learning project, explain why each fixture exists and add cases from an application you own. Our portfolio evidence guide explains why inspectable work is more useful than a raw activity count.
Methodology and limitations
This is an original local teaching experiment, not independent validation of the Go team’s benchmark. The run is sequential, with no randomized API order, background-load control or independent second-machine replication. A simple string-heavy payload does not cover streaming, custom marshalers, deeply nested structures, multilingual strings or large maps.
We compare the v1 and v2 APIs within Go 1.27.3. This is not a comparison with a pre-1.27 legacy runtime or a nojsonv2 backend. The seven differences were deliberately selected, and the sixteen matched expectations validate the exercise’s stated cases rather than proving complete compatibility.
Build Your Vision, Perfectly Tailored
Get a custom-built website or application that matches your vision and needs. Stand out from the crowd with a solution designed just for you—professional, scalable, and seamless.
Get StartedThe practical value of a Go JSON v2 migration report is its audit trail: inputs, expected outputs, actual errors, exact runtime and repeated observations. Keep that trail when your own results differ. Update the contract decision first, and only then decide whether a measured performance change is useful.
