Skip to content

BuildNot yet confirmed elsewhere1 publisher3 min readPublished

Upgrading to Go 1.27 changes the engine under encoding/json before you touch an import

Go 1.27 now runs encoding/json on the v2 engine, so error message text can change on upgrade, according to a dev.to post. Changing an import to encoding/json/v2 flips four defaults, and some of the changes raise no error.

The Engineer · Build desk

How we use AISend a correction

Illustration accompanying Upgrading to Go 1.27 changes the engine under encoding/json before you touch an import
Generated illustration
Some v2 behaviour changes raise no error at all Differences between encoding/json and encoding/json/v2 in the dev.to post's run that change output or input handling without returning an error

Three of the six differences between encoding/json and encoding/json/v2 in the post's run raise no error: nil slices, omitempty and case matching. The other three, duplicate names, invalid UTF-8 and time.Duration, produce an error in v2.

Some v2 behaviour changes raise no error at all
SharePartWholePercentClaim
Differences with no error in v23 differences6 differences50% (computed)19

What happened

  • The v1 API stays supported, and nobody is required to migrate to encoding/json/v2.
  • If an upgrade breaks something, setting GOEXPERIMENT=nojsonv2 brings back the original code, though the release notes say a future release is expected to drop that opt-out.
  • In the post's own run on a four-field struct, six behaviours differ between encoding/json and encoding/json/v2.
  • The encoding/json documentation has a "Migrating to v2" section that lists every behaviour difference and the option that controls it.

Why it matters

  • constraint Any test that compares err.Error() to a fixed string can fail on upgrade with no behaviour change, and the fix is to assert on error types.
  • exposure Services that accept inconsistently cased keys from clients get empty fields and a nil error under v2, so the failure appears downstream as missing data.
  • decision Each package moved to v2 forces a per-behaviour choice: take the new default, or pass the option that restores the old one, such as FormatNilSliceAsNull.

The upgrade and the import change are different operations. The post says that since Go 1.27 the old package runs on the v2 engine with compatibility options switched on [2]. The release notes, as the post quotes them, say encoding/json "is now backed by the v2 implementation. Marshaling and unmarshaling behavior is preserved, but the exact text of error messages may differ." [3]

The import change is where output moves. The post's test program pushes the same values through both packages using a four-field User struct [1]. One line of its output shows two changes at once. v1 wrote {"name":"ana","tags":null}. v2 wrote {"name":"ana","tags":[],"admin":false,"retries":0} [7].

The second change comes from omitempty. In v1 it skips fields holding false, 0, or a nil pointer or interface. v2 skips a field only when its encoding would be null, "", {} or [] [9]. The package documentation, per the post, says existing uses on a bool, number, pointer or interface "should migrate to specifying omitzero instead" [10].

Count the differences in that run and you get six [18]. Three of them fail loudly: duplicate names, invalid UTF-8 and time.Duration [19]. On invalid UTF-8, v1 returned "caf�" with a nil error, and v2 returns an error naming the path "/name" and offset 12 [15]. On time.Duration, v2 fails with "no default representation" [16].

The other three change data and say nothing: nil slices, omitempty and case matching [19]. For case, v2 leaves the field empty and returns a nil error. The post says you will learn about it from missing data, not from logs [11]. No grep finds a missing field.

Duplicate keys sit in between. v1 let the last "name" win, so {"name":"alice","name":"mallory"} became mallory. v2 rejects the input unless you pass jsontext.AllowDuplicateNames(true) [12]. The post points to the security section of the v2 documentation, which explains why this matters when two services parse the same request differently [14]. After the v2 error, the destination already held "alice" [13]. We would not reuse that struct.

One caveat on the numbers. This is one struct on one run. It shows which behaviours exist, not how many of your payloads hit them. Whether six differences matter to you depends on whether your clients send mixed-case keys, whether downstream code checks === null on a field your Go service leaves nil, and whether anything marshals a time.Duration. The post does not show the v1 error strings before and after the upgrade, so the size of the error-text drift is unmeasured.

In our view the order is: upgrade, move tests to error types, then migrate packages to v2 one at a time. That is the right order for services that take payloads from clients you do not control. The upgrade has one known seam, and the import change has six [18].

What to watch

  • The release that removes the GOEXPERIMENT=nojsonv2 opt-out, which ends the one-line rollback for the engine swap.
  • A published before-and-after list of v1 error strings under Go 1.27, which would size the drift that the post does not measure.

Clarity's read

What the record supports and how the coverage leans. The claims behind it follow.

Reality

Evidence60
Adoption
Insufficient
Hype gap−5
Incentives
Insufficient
Confidence62
Why these scores

Claim ledger

Ranked by verification strength, evidence, and original report placement.

  1. [1]

    The post's program marshals and unmarshals the same values with encoding/json and encoding/json/v2 using a User struct with four fields: Name, Tags, Admin and Retries.

    ReportedSupportedSource: dev.to post by ImportStatic2 sources— create a free account to open themView cited source
  2. [2]

    Since Go 1.27, the old encoding/json package is implemented on top of the v2 engine with a set of compatibility options switched on.

    ReportedSupportedSource: dev.to post by ImportStaticView cited source
  3. [3]

    The Go 1.27 release notes, as quoted in the post, say encoding/json "is now backed by the v2 implementation. Marshaling and unmarshaling behavior is preserved, but the exact text of error messages may differ."

    ReportedSupportedSource: Go 1.27 release notes, quoted by the dev.to postView cited source

Sources

1 independent publisher whose own reporting we read for this story.

  1. dev.to

    1 article · October 11, 2026

    Go 1.27's JSON v2 is already under your encoding/json

Share your take

Let Clarity write the post for you.

Signed-in readers get a short post drafted on this story in the register they choose — narrative, analytical, or a direct position — editable to the last word before it goes anywhere. The share buttons at the top of this story work without an account.

Topics and entities

Follow any of these and your For You feed starts watching them — no settings page required.

Topics

  • Go standard libraryFollow
  • JSON SerializationFollow
  • Backward Compatibility and DeprecationsFollow

Entities

Loading related stories