<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/wwgzuwdgd" -->

---
title: Go JSON v2 Migration: What Breaks in Go 1.27 | daily.dev
description: Go 1.27 ships encoding/json/v2 and quietly reimplements the old encoding/json on top of the new engine, meaning every Go 1.27 user is already running v2...
canonical: https://daily.dev/posts/wwgzuwdgd
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Go JSON v2 Migration: What Breaks in Go 1.27 | daily.dev
og:description: Go 1.27 ships encoding/json/v2 and quietly reimplements the old encoding/json on top of the new engine, meaning every Go 1.27 user is already running v2...
og:url: https://daily.dev/posts/wwgzuwdgd
og:image: https://api.daily.dev/og/posts/WwGZuwdGd.png
og:image:alt: Post cover image
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Go JSON v2 Migration: What Breaks in Go 1.27

**[Go Developers](https://daily.dev/sources/golangnuts)** · [@func25](https://daily.dev/func25) · 45 upvotes · 2 comments

## Summary

Go 1.27 ships encoding/json/v2 and quietly reimplements the old encoding/json on top of the new engine, meaning every Go 1.27 user is already running v2 internals even without changing imports. Side-by-side testing of identical structs reveals concrete behavior differences: nil slices marshal as [] instead of null, omitempty no longer drops false/0 (use omitzero instead), case-insensitive field matching is gone, duplicate JSON keys now error instead of silently using the last value, invalid UTF-8 errors instead of being replaced, time.Duration fails to marshal without FormatDurationAsNano(true), and HTML escaping is off by default. Benchmarks on Go 1.27.1 show unmarshal ~25% faster with 29% fewer allocations, but marshal ~39% slower on one payload, contradicting the release notes' 'broadly at parity' claim. A GOEXPERIMENT=nojsonv2 opt-out exists but is slated for removal, and a DefaultOptionsV1()-based option-by-option migration path plus the jsonsplit package are offered for safer large-codebase upgrades.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://importstatic.com/go/go-json-v2-migration>

## Community discussion

Top comments from developers on daily.dev.

**@ristotoldsep** · 4 upvotes

> nil slices changing from null to [] will break exactly the clients nobody documented.

**@ahmetozel** · 2 upvotes

> The duplicate-key example is particularly useful because the destination already contains Alice even though decoding returned an error. That is a migration risk if a caller logs the error but continues to use a partially populated request object.
>
> I would decode into a fresh temporary value and publish it to the handler only after success, then add a test with an initially populated destination as well as an empty one. That separates rejection of malformed input from accidental reuse of old or partial fields. The option-by-option rollout could then include this caller behavior, not just...

## Similar posts on daily.dev

- [Go 1.27 interactive tour](https://daily.dev/posts/go-1-27-interactive-tour-dvxr0krfs) · Lobsters · 11 upvotes · 0 comments

---

Tags: [#golang](https://daily.dev/tags/golang)

[View this post on daily.dev](https://daily.dev/posts/wwgzuwdgd)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"DiscussionForumPosting","mainEntityOfPage":"https://daily.dev/posts/wwgzuwdgd","headline":"Go JSON v2 Migration: What Breaks in Go 1.27","text":"Shared: Go JSON v2 Migration: What Breaks in Go 1.27","url":"https://daily.dev/posts/wwgzuwdgd","datePublished":"2026-10-05T00:39:47.784Z","dateModified":"2026-10-05T00:40:57.743Z","author":{"@type":"Person","name":"Phuong Le","url":"https://daily.dev/func25","image":"https://media.daily.dev/image/upload/s--HbVBqLGd--/f_auto,q_auto/v1697862633/avatars/avatar_s1rxG2e5sEfVSY1wCzdr8","interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"EndorseAction"},"userInteractionCount":8715}},"image":"https://media.daily.dev/image/upload/s--58gMhC4P--/f_auto/v1722860399/public/Placeholder%2012","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":45},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":2}],"sharedContent":{"@type":"WebPage","url":"https://api.daily.dev/r/x1wYxeVwv"},"comment":[{"@type":"Comment","text":"nil slices changing from null to [] will break exactly the clients nobody documented.","datePublished":"2026-10-05T12:29:01.645Z","url":"https://daily.dev/posts/WwGZuwdGd#c-7gEl2ZaZB","author":{"@type":"Person","name":"Risto Tõldsep","url":"https://daily.dev/ristotoldsep","image":"https://lh3.googleusercontent.com/a/ACg8ocLDWc6mZn0JwNmXXw6WY0L_HJ6pegzRttooC5VgtXESHSj3MNYx=s96-c"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":4}},{"@type":"Comment","text":"The duplicate-key example is particularly useful because the destination already contains Alice even though decoding returned an error. That is a migration risk if a caller logs the error but continues to use a partially populated request object.\nI would decode into a fresh temporary value and publish it to the handler only after success, then add a test with an initially populated destination as well as an empty one. That separates rejection of malformed input from accidental reuse of old or partial fields. The option-by-option rollout could then include this caller behavior, not just comparisons of serialized bytes.","datePublished":"2026-10-05T12:25:10.634Z","url":"https://daily.dev/posts/WwGZuwdGd#c-1wWauLJxs","author":{"@type":"Person","name":"Ahmet Özel","url":"https://daily.dev/ahmetozel","image":"https://avatars.githubusercontent.com/u/70992231?v=4"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2}}],"isPartOf":{"@type":"WebPage","url":"https://daily.dev/squads/golangnuts","name":"Go Developers"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Go Developers","item":"https://daily.dev/squads/golangnuts"},{"@type":"ListItem","position":3,"name":"Go JSON v2 Migration: What Breaks in Go 1.27"}]}
```

