# How v1 Sits on Top of v2

> Source: https://www.ymotongpoo.com/books/go-json-v2-history/50-v1_on_v2/


## Putting v1 on top of v2

In Go 1.27, `encoding/json/v2` and `encoding/json/jsontext` became official packages. At the same time, `encoding/json` was reimplemented on top of v2.

From the [release notes](https://go.dev/doc/go1.27):

> The encoding/json package is now backed by the v2 implementation. Marshaling and unmarshaling behavior is preserved, but the exact text of error messages may differ.

The October 2020 README said it would be convenient if v1 could be implemented in terms of v2. That has come true, exactly as written.

At first glance, this looks like nothing but good news. It reads as if people who keep writing `json.Marshal` get the benefit of v2's faster implementation without doing anything, and no migration is needed.

## The full list of legacy behavior flags

So how is v1's behavior preserved? The `encoding/json` documentation has the answer.

> As mentioned, the entirety of v1 is implemented in terms of v2, where options are implicitly specified to opt into legacy behavior. For example, [Marshal] directly calls [jsonv2.Marshal] with [DefaultOptionsV1].

The contents of that `DefaultOptionsV1` are defined in an internal package of the standard library. Open `internal/jsonflags/flags.go` and you find this:

```go
// Marshal and Unmarshal flags (for v1).
const (
	_ Bools = (maxArshalV2Flag >> 1) << iota

	CallMethodsWithLegacySemantics  // marshal or unmarshal
	FormatByteArrayAsArray          // marshal or unmarshal
	FormatBytesWithLegacySemantics  // marshal or unmarshal
	FormatDurationAsNano            // marshal or unmarshal
	MatchCaseSensitiveDelimiter     // marshal or unmarshal
	MergeWithLegacySemantics        // unmarshal
	OmitEmptyWithLegacySemantics    // marshal
	ParseBytesWithLooseRFC4648      // unmarshal
	ParseTimeWithLooseRFC3339       // unmarshal
	ReportErrorsWithLegacySemantics // marshal or unmarshal
	StringifyWithLegacySemantics    // marshal or unmarshal
	UnmarshalAnyWithRawNumber       // unmarshal; for internal use by jsonv1.Decoder.UseNumber
	UnmarshalArrayFromAnyLength     // unmarshal

	maxArshalV1Flag
)
```

Fourteen years' worth of "things we wanted to fix but couldn't" line up here, each with a name.

`FormatDurationAsNano` is the true identity of the issue Russ Cox closed in 2017, saying this fundamental part would not change. Some readers will also recognize `ParseTimeWithLooseRFC3339` and `ParseBytesWithLooseRFC4648`: RFC compliance was loose, and inputs that should have been rejected were accepted. `UnmarshalArrayFromAnyLength` lets a three-element JSON array be unmarshaled into a five-element Go array. `OmitEmptyWithLegacySemantics` signals that the meaning of `omitempty` itself changed in v2: the criterion moved from Go's type system to JSON's type system.

The `WithLegacySemantics` suffix on 6 of the 13 flags means "the semantics of the past." Merely selecting v1's behavior could have been named `V1Semantics`, but they chose `Legacy`. What the 2020 README from chapter 3 called "certain behaviors that are now considered mistakes" has become an identifier in the standard library.

None of these behaviors were deleted or hidden. They are preserved as v1 behavior, in a form where each one can be specified individually when needed.

## Fine-tuning behavior with DefaultOptionsV1

Run it, and you can see exactly what this row of flags does.

* https://go.dev/play/p/3GBPGVWQ-QY

```go
package main

import (
	jsonv1 "encoding/json"
	"encoding/json/jsontext"
	jsonv2 "encoding/json/v2"
	"fmt"
)

type Doc struct {
	Tags  []string          `json:"tags"`
	Attrs map[string]string `json:"attrs"`
	Body  string            `json:"body"`
}

func main() {
	d := Doc{Body: "<b>hi</b>"} // Tags and Attrs are both nil

	b1, _ := jsonv1.Marshal(d)
	fmt.Printf("v1                       : %s\n", b1)

	b2, _ := jsonv2.Marshal(d)
	fmt.Printf("v2                       : %s\n", b2)

	b3, _ := jsonv2.Marshal(d, jsonv1.DefaultOptionsV1())
	fmt.Printf("v2 + DefaultOptionsV1    : %s\n", b3)
	fmt.Printf("v1 と一致するか          : %v\n", string(b1) == string(b3))

	// Start from v1 and flip only HTML escaping to the v2 side
	b4, _ := jsonv2.Marshal(d, jsonv1.DefaultOptionsV1(), jsontext.EscapeForHTML(false))
	fmt.Printf("v1 - HTMLエスケープ      : %s\n", b4)

	// Start from v2 and flip only the nil-slice representation to the v1 side
	b5, _ := jsonv2.Marshal(d, jsonv2.FormatNilSliceAsNull(true))
	fmt.Printf("v2 + nilスライスをnullに : %s\n", b5)
}
```

```
v1                       : {"tags":null,"attrs":null,"body":"\u003cb\u003ehi\u003c/b\u003e"}
v2                       : {"tags":[],"attrs":{},"body":"<b>hi</b>"}
v2 + DefaultOptionsV1    : {"tags":null,"attrs":null,"body":"\u003cb\u003ehi\u003c/b\u003e"}
v1 と一致するか          : true
v1 - HTMLエスケープ      : {"tags":null,"attrs":null,"body":"<b>hi</b>"}
v2 + nilスライスをnullに : {"tags":null,"attrs":{},"body":"<b>hi</b>"}
```

The third line matches v1's output byte for byte. You call the v2 API, and the result is identical to v1.

Then look at lines 4 and 5: you can flip a single behavior toward v2 starting from v1, or a single behavior toward v1 starting from v2. Options specified later take precedence, so the pattern of placing `DefaultOptionsV1()` first and then overriding individual options works.

For 14 years, there were only two choices: keep using v1 as it was, or accept that v1 would break. Now you can specify any point between v1 and v2, so compatibility no longer forces you to pick one side or the other.

The `encoding/json` [documentation](https://pkg.go.dev/encoding/json@go1.27.0#hdr-Migrating_to_v2) explicitly presents this usage as a means of incremental migration.

```go
jsonv1.Marshal(v)
// Default v1 behavior

jsonv2.Marshal(v, jsonv1.DefaultOptionsV1())
// Semantically equivalent to jsonv1.Marshal

jsonv2.Marshal(v, jsonv1.DefaultOptionsV1(), jsontext.AllowDuplicateNames(false))
// Mostly v1, but adopts v2's behavior of rejecting duplicate names

jsonv2.Marshal(v, jsonv1.CallMethodsWithLegacySemantics(true))
// Mostly v2, but adopts v1's method-calling behavior

jsonv2.Marshal(v)
// Default v2 behavior
```

