Jelajahi Sumber

cmd/viewer, types/views: implement support for json/v2 (#16852)

This adds support for having every viewer type implement
jsonv2.MarshalerTo and jsonv2.UnmarshalerFrom.

This provides a significant boost in performance
as the json package no longer needs to validate
the entirety of the JSON value outputted by MarshalJSON,
nor does it need to identify the boundaries of a JSON value
in order to call UnmarshalJSON.

For deeply nested and recursive MarshalJSON or UnmarshalJSON calls,
this can improve runtime from O(N²) to O(N).

This still references "github.com/go-json-experiment/json"
instead of the experimental "encoding/json/v2" package
now available in Go 1.25 under goexperiment.jsonv2
so that code still builds without the experiment tag.
Of note, the "github.com/go-json-experiment/json" package
aliases the standard library under the right build conditions.

Updates tailscale/corp#791

Signed-off-by: Joe Tsai <[email protected]>
Joe Tsai 6 bulan lalu
induk
melakukan
fbb91758ac

+ 6 - 6
cmd/cloner/cloner.go

@@ -136,13 +136,13 @@ func gen(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named) {
 					writef("if src.%s[i] == nil { dst.%s[i] = nil } else {", fname, fname)
 					writef("if src.%s[i] == nil { dst.%s[i] = nil } else {", fname, fname)
 					if codegen.ContainsPointers(ptr.Elem()) {
 					if codegen.ContainsPointers(ptr.Elem()) {
 						if _, isIface := ptr.Elem().Underlying().(*types.Interface); isIface {
 						if _, isIface := ptr.Elem().Underlying().(*types.Interface); isIface {
-							it.Import("tailscale.com/types/ptr")
+							it.Import("", "tailscale.com/types/ptr")
 							writef("\tdst.%s[i] = ptr.To((*src.%s[i]).Clone())", fname, fname)
 							writef("\tdst.%s[i] = ptr.To((*src.%s[i]).Clone())", fname, fname)
 						} else {
 						} else {
 							writef("\tdst.%s[i] = src.%s[i].Clone()", fname, fname)
 							writef("\tdst.%s[i] = src.%s[i].Clone()", fname, fname)
 						}
 						}
 					} else {
 					} else {
-						it.Import("tailscale.com/types/ptr")
+						it.Import("", "tailscale.com/types/ptr")
 						writef("\tdst.%s[i] = ptr.To(*src.%s[i])", fname, fname)
 						writef("\tdst.%s[i] = ptr.To(*src.%s[i])", fname, fname)
 					}
 					}
 					writef("}")
 					writef("}")
@@ -165,7 +165,7 @@ func gen(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named) {
 				writef("dst.%s = src.%s.Clone()", fname, fname)
 				writef("dst.%s = src.%s.Clone()", fname, fname)
 				continue
 				continue
 			}
 			}
-			it.Import("tailscale.com/types/ptr")
+			it.Import("", "tailscale.com/types/ptr")
 			writef("if dst.%s != nil {", fname)
 			writef("if dst.%s != nil {", fname)
 			if _, isIface := base.Underlying().(*types.Interface); isIface && hasPtrs {
 			if _, isIface := base.Underlying().(*types.Interface); isIface && hasPtrs {
 				writef("\tdst.%s = ptr.To((*src.%s).Clone())", fname, fname)
 				writef("\tdst.%s = ptr.To((*src.%s).Clone())", fname, fname)
@@ -197,13 +197,13 @@ func gen(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named) {
 					writef("\t\tif v == nil { dst.%s[k] = nil } else {", fname)
 					writef("\t\tif v == nil { dst.%s[k] = nil } else {", fname)
 					if base := elem.Elem().Underlying(); codegen.ContainsPointers(base) {
 					if base := elem.Elem().Underlying(); codegen.ContainsPointers(base) {
 						if _, isIface := base.(*types.Interface); isIface {
 						if _, isIface := base.(*types.Interface); isIface {
-							it.Import("tailscale.com/types/ptr")
+							it.Import("", "tailscale.com/types/ptr")
 							writef("\t\t\tdst.%s[k] = ptr.To((*v).Clone())", fname)
 							writef("\t\t\tdst.%s[k] = ptr.To((*v).Clone())", fname)
 						} else {
 						} else {
 							writef("\t\t\tdst.%s[k] = v.Clone()", fname)
 							writef("\t\t\tdst.%s[k] = v.Clone()", fname)
 						}
 						}
 					} else {
 					} else {
-						it.Import("tailscale.com/types/ptr")
+						it.Import("", "tailscale.com/types/ptr")
 						writef("\t\t\tdst.%s[k] = ptr.To(*v)", fname)
 						writef("\t\t\tdst.%s[k] = ptr.To(*v)", fname)
 					}
 					}
 					writef("}")
 					writef("}")
@@ -224,7 +224,7 @@ func gen(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named) {
 				writef("\t}")
 				writef("\t}")
 				writef("}")
 				writef("}")
 			} else {
 			} else {
-				it.Import("maps")
+				it.Import("", "maps")
 				writef("\tdst.%s = maps.Clone(src.%s)", fname, fname)
 				writef("\tdst.%s = maps.Clone(src.%s)", fname, fname)
 			}
 			}
 		case *types.Interface:
 		case *types.Interface:

+ 1 - 1
cmd/stund/depaware.txt

@@ -2,7 +2,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
 
 
         github.com/beorn7/perks/quantile                             from github.com/prometheus/client_golang/prometheus
         github.com/beorn7/perks/quantile                             from github.com/prometheus/client_golang/prometheus
      💣 github.com/cespare/xxhash/v2                                 from github.com/prometheus/client_golang/prometheus
      💣 github.com/cespare/xxhash/v2                                 from github.com/prometheus/client_golang/prometheus
-        github.com/go-json-experiment/json                           from tailscale.com/types/opt
+        github.com/go-json-experiment/json                           from tailscale.com/types/opt+
         github.com/go-json-experiment/json/internal                  from github.com/go-json-experiment/json+
         github.com/go-json-experiment/json/internal                  from github.com/go-json-experiment/json+
         github.com/go-json-experiment/json/internal/jsonflags        from github.com/go-json-experiment/json+
         github.com/go-json-experiment/json/internal/jsonflags        from github.com/go-json-experiment/json+
         github.com/go-json-experiment/json/internal/jsonopts         from github.com/go-json-experiment/json+
         github.com/go-json-experiment/json/internal/jsonopts         from github.com/go-json-experiment/json+

+ 265 - 23
cmd/viewer/tests/tests_view.go

@@ -6,10 +6,12 @@
 package tests
 package tests
 
 
 import (
 import (
-	"encoding/json"
+	jsonv1 "encoding/json"
 	"errors"
 	"errors"
 	"net/netip"
 	"net/netip"
 
 
+	jsonv2 "github.com/go-json-experiment/json"
+	"github.com/go-json-experiment/json/jsontext"
 	"golang.org/x/exp/constraints"
 	"golang.org/x/exp/constraints"
 	"tailscale.com/types/views"
 	"tailscale.com/types/views"
 )
 )
@@ -44,8 +46,17 @@ func (v StructWithPtrsView) AsStruct() *StructWithPtrs {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v StructWithPtrsView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v StructWithPtrsView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v StructWithPtrsView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *StructWithPtrsView) UnmarshalJSON(b []byte) error {
 func (v *StructWithPtrsView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -54,7 +65,20 @@ func (v *StructWithPtrsView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x StructWithPtrs
 	var x StructWithPtrs
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *StructWithPtrsView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x StructWithPtrs
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -108,8 +132,17 @@ func (v StructWithoutPtrsView) AsStruct() *StructWithoutPtrs {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v StructWithoutPtrsView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v StructWithoutPtrsView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
 
 
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v StructWithoutPtrsView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *StructWithoutPtrsView) UnmarshalJSON(b []byte) error {
 func (v *StructWithoutPtrsView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -118,7 +151,20 @@ func (v *StructWithoutPtrsView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x StructWithoutPtrs
 	var x StructWithoutPtrs
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *StructWithoutPtrsView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x StructWithoutPtrs
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -162,8 +208,17 @@ func (v MapView) AsStruct() *Map {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v MapView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v MapView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v MapView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *MapView) UnmarshalJSON(b []byte) error {
 func (v *MapView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -172,7 +227,20 @@ func (v *MapView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x Map
 	var x Map
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *MapView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x Map
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -268,8 +336,17 @@ func (v StructWithSlicesView) AsStruct() *StructWithSlices {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v StructWithSlicesView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v StructWithSlicesView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
 
 
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v StructWithSlicesView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *StructWithSlicesView) UnmarshalJSON(b []byte) error {
 func (v *StructWithSlicesView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -278,7 +355,20 @@ func (v *StructWithSlicesView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x StructWithSlices
 	var x StructWithSlices
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *StructWithSlicesView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x StructWithSlices
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -342,8 +432,17 @@ func (v StructWithEmbeddedView) AsStruct() *StructWithEmbedded {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v StructWithEmbeddedView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v StructWithEmbeddedView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
 
 
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v StructWithEmbeddedView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *StructWithEmbeddedView) UnmarshalJSON(b []byte) error {
 func (v *StructWithEmbeddedView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -352,7 +451,20 @@ func (v *StructWithEmbeddedView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x StructWithEmbedded
 	var x StructWithEmbedded
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *StructWithEmbeddedView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x StructWithEmbedded
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -398,8 +510,17 @@ func (v GenericIntStructView[T]) AsStruct() *GenericIntStruct[T] {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v GenericIntStructView[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v GenericIntStructView[T]) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v GenericIntStructView[T]) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *GenericIntStructView[T]) UnmarshalJSON(b []byte) error {
 func (v *GenericIntStructView[T]) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -408,7 +529,20 @@ func (v *GenericIntStructView[T]) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x GenericIntStruct[T]
 	var x GenericIntStruct[T]
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *GenericIntStructView[T]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x GenericIntStruct[T]
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -470,8 +604,17 @@ func (v GenericNoPtrsStructView[T]) AsStruct() *GenericNoPtrsStruct[T] {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v GenericNoPtrsStructView[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v GenericNoPtrsStructView[T]) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v GenericNoPtrsStructView[T]) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *GenericNoPtrsStructView[T]) UnmarshalJSON(b []byte) error {
 func (v *GenericNoPtrsStructView[T]) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -480,7 +623,20 @@ func (v *GenericNoPtrsStructView[T]) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x GenericNoPtrsStruct[T]
 	var x GenericNoPtrsStruct[T]
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *GenericNoPtrsStructView[T]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x GenericNoPtrsStruct[T]
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -542,8 +698,17 @@ func (v GenericCloneableStructView[T, V]) AsStruct() *GenericCloneableStruct[T,
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v GenericCloneableStructView[T, V]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v GenericCloneableStructView[T, V]) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v GenericCloneableStructView[T, V]) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *GenericCloneableStructView[T, V]) UnmarshalJSON(b []byte) error {
 func (v *GenericCloneableStructView[T, V]) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -552,7 +717,20 @@ func (v *GenericCloneableStructView[T, V]) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x GenericCloneableStruct[T, V]
 	var x GenericCloneableStruct[T, V]
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *GenericCloneableStructView[T, V]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x GenericCloneableStruct[T, V]
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -617,8 +795,17 @@ func (v StructWithContainersView) AsStruct() *StructWithContainers {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v StructWithContainersView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v StructWithContainersView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v StructWithContainersView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *StructWithContainersView) UnmarshalJSON(b []byte) error {
 func (v *StructWithContainersView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -627,7 +814,20 @@ func (v *StructWithContainersView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x StructWithContainers
 	var x StructWithContainers
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *StructWithContainersView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x StructWithContainers
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -689,8 +889,17 @@ func (v StructWithTypeAliasFieldsView) AsStruct() *StructWithTypeAliasFields {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v StructWithTypeAliasFieldsView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v StructWithTypeAliasFieldsView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
 
 
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v StructWithTypeAliasFieldsView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *StructWithTypeAliasFieldsView) UnmarshalJSON(b []byte) error {
 func (v *StructWithTypeAliasFieldsView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -699,7 +908,20 @@ func (v *StructWithTypeAliasFieldsView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x StructWithTypeAliasFields
 	var x StructWithTypeAliasFields
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *StructWithTypeAliasFieldsView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x StructWithTypeAliasFields
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -787,10 +1009,17 @@ func (v GenericTypeAliasStructView[T, T2, V2]) AsStruct() *GenericTypeAliasStruc
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
+// MarshalJSON implements [jsonv1.Marshaler].
 func (v GenericTypeAliasStructView[T, T2, V2]) MarshalJSON() ([]byte, error) {
 func (v GenericTypeAliasStructView[T, T2, V2]) MarshalJSON() ([]byte, error) {
-	return json.Marshal(v.ж)
+	return jsonv1.Marshal(v.ж)
 }
 }
 
 
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v GenericTypeAliasStructView[T, T2, V2]) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *GenericTypeAliasStructView[T, T2, V2]) UnmarshalJSON(b []byte) error {
 func (v *GenericTypeAliasStructView[T, T2, V2]) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -799,7 +1028,20 @@ func (v *GenericTypeAliasStructView[T, T2, V2]) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x GenericTypeAliasStruct[T, T2, V2]
 	var x GenericTypeAliasStruct[T, T2, V2]
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *GenericTypeAliasStructView[T, T2, V2]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x GenericTypeAliasStruct[T, T2, V2]
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x

+ 32 - 8
cmd/viewer/viewer.go

@@ -49,8 +49,17 @@ func (v {{.ViewName}}{{.TypeParamNames}}) AsStruct() *{{.StructName}}{{.TypePara
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v {{.ViewName}}{{.TypeParamNames}}) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v {{.ViewName}}{{.TypeParamNames}}) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v {{.ViewName}}{{.TypeParamNames}}) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *{{.ViewName}}{{.TypeParamNames}}) UnmarshalJSON(b []byte) error {
 func (v *{{.ViewName}}{{.TypeParamNames}}) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -59,10 +68,23 @@ func (v *{{.ViewName}}{{.TypeParamNames}}) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x {{.StructName}}{{.TypeParamNames}}
 	var x {{.StructName}}{{.TypeParamNames}}
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *{{.ViewName}}{{.TypeParamNames}}) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x {{.StructName}}{{.TypeParamNames}}
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
-	v.ж=&x
+	v.ж = &x
 	return nil
 	return nil
 }
 }
 
 
@@ -125,8 +147,10 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, _ *
 	if !ok || codegen.IsViewType(t) {
 	if !ok || codegen.IsViewType(t) {
 		return
 		return
 	}
 	}
-	it.Import("encoding/json")
-	it.Import("errors")
+	it.Import("jsonv1", "encoding/json")
+	it.Import("jsonv2", "github.com/go-json-experiment/json")
+	it.Import("", "github.com/go-json-experiment/json/jsontext")
+	it.Import("", "errors")
 
 
 	args := struct {
 	args := struct {
 		StructName     string
 		StructName     string
@@ -182,11 +206,11 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, _ *
 			switch elem.String() {
 			switch elem.String() {
 			case "byte":
 			case "byte":
 				args.FieldType = it.QualifiedName(fieldType)
 				args.FieldType = it.QualifiedName(fieldType)
-				it.Import("tailscale.com/types/views")
+				it.Import("", "tailscale.com/types/views")
 				writeTemplate("byteSliceField")
 				writeTemplate("byteSliceField")
 			default:
 			default:
 				args.FieldType = it.QualifiedName(elem)
 				args.FieldType = it.QualifiedName(elem)
-				it.Import("tailscale.com/types/views")
+				it.Import("", "tailscale.com/types/views")
 				shallow, deep, base := requiresCloning(elem)
 				shallow, deep, base := requiresCloning(elem)
 				if deep {
 				if deep {
 					switch elem.Underlying().(type) {
 					switch elem.Underlying().(type) {
@@ -252,7 +276,7 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, _ *
 				writeTemplate("unsupportedField")
 				writeTemplate("unsupportedField")
 				continue
 				continue
 			}
 			}
-			it.Import("tailscale.com/types/views")
+			it.Import("", "tailscale.com/types/views")
 			args.MapKeyType = it.QualifiedName(key)
 			args.MapKeyType = it.QualifiedName(key)
 			mElem := m.Elem()
 			mElem := m.Elem()
 			var template string
 			var template string

+ 6 - 6
cmd/viewer/viewer_test.go

@@ -20,19 +20,19 @@ func TestViewerImports(t *testing.T) {
 		name        string
 		name        string
 		content     string
 		content     string
 		typeNames   []string
 		typeNames   []string
-		wantImports []string
+		wantImports [][2]string
 	}{
 	}{
 		{
 		{
 			name:        "Map",
 			name:        "Map",
 			content:     `type Test struct { Map map[string]int }`,
 			content:     `type Test struct { Map map[string]int }`,
 			typeNames:   []string{"Test"},
 			typeNames:   []string{"Test"},
-			wantImports: []string{"tailscale.com/types/views"},
+			wantImports: [][2]string{{"", "tailscale.com/types/views"}},
 		},
 		},
 		{
 		{
 			name:        "Slice",
 			name:        "Slice",
 			content:     `type Test struct { Slice []int }`,
 			content:     `type Test struct { Slice []int }`,
 			typeNames:   []string{"Test"},
 			typeNames:   []string{"Test"},
-			wantImports: []string{"tailscale.com/types/views"},
+			wantImports: [][2]string{{"", "tailscale.com/types/views"}},
 		},
 		},
 	}
 	}
 	for _, tt := range tests {
 	for _, tt := range tests {
@@ -68,9 +68,9 @@ func TestViewerImports(t *testing.T) {
 				genView(&output, tracker, namedType, pkg)
 				genView(&output, tracker, namedType, pkg)
 			}
 			}
 
 
-			for _, pkgName := range tt.wantImports {
-				if !tracker.Has(pkgName) {
-					t.Errorf("missing import %q", pkgName)
+			for _, pkg := range tt.wantImports {
+				if !tracker.Has(pkg[0], pkg[1]) {
+					t.Errorf("missing import %q", pkg)
 				}
 				}
 			}
 			}
 		})
 		})

+ 27 - 3
drive/drive_view.go

@@ -6,9 +6,11 @@
 package drive
 package drive
 
 
 import (
 import (
-	"encoding/json"
+	jsonv1 "encoding/json"
 	"errors"
 	"errors"
 
 
+	jsonv2 "github.com/go-json-experiment/json"
+	"github.com/go-json-experiment/json/jsontext"
 	"tailscale.com/types/views"
 	"tailscale.com/types/views"
 )
 )
 
 
@@ -42,8 +44,17 @@ func (v ShareView) AsStruct() *Share {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v ShareView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v ShareView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v ShareView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *ShareView) UnmarshalJSON(b []byte) error {
 func (v *ShareView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -52,7 +63,20 @@ func (v *ShareView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x Share
 	var x Share
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *ShareView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x Share
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x

+ 1 - 1
go.mod

@@ -33,7 +33,7 @@ require (
 	github.com/frankban/quicktest v1.14.6
 	github.com/frankban/quicktest v1.14.6
 	github.com/fxamacker/cbor/v2 v2.7.0
 	github.com/fxamacker/cbor/v2 v2.7.0
 	github.com/gaissmai/bart v0.18.0
 	github.com/gaissmai/bart v0.18.0
-	github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874
+	github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced
 	github.com/go-logr/zapr v1.3.0
 	github.com/go-logr/zapr v1.3.0
 	github.com/go-ole/go-ole v1.3.0
 	github.com/go-ole/go-ole v1.3.0
 	github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737
 	github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737

+ 2 - 2
go.sum

@@ -345,8 +345,8 @@ github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0q
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY=
-github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
+github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I=
+github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=

+ 171 - 15
ipn/ipn_view.go

@@ -6,10 +6,12 @@
 package ipn
 package ipn
 
 
 import (
 import (
-	"encoding/json"
+	jsonv1 "encoding/json"
 	"errors"
 	"errors"
 	"net/netip"
 	"net/netip"
 
 
+	jsonv2 "github.com/go-json-experiment/json"
+	"github.com/go-json-experiment/json/jsontext"
 	"tailscale.com/drive"
 	"tailscale.com/drive"
 	"tailscale.com/tailcfg"
 	"tailscale.com/tailcfg"
 	"tailscale.com/types/opt"
 	"tailscale.com/types/opt"
@@ -48,8 +50,17 @@ func (v LoginProfileView) AsStruct() *LoginProfile {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v LoginProfileView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v LoginProfileView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v LoginProfileView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *LoginProfileView) UnmarshalJSON(b []byte) error {
 func (v *LoginProfileView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -58,7 +69,20 @@ func (v *LoginProfileView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x LoginProfile
 	var x LoginProfile
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *LoginProfileView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x LoginProfile
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -114,8 +138,17 @@ func (v PrefsView) AsStruct() *Prefs {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v PrefsView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v PrefsView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v PrefsView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *PrefsView) UnmarshalJSON(b []byte) error {
 func (v *PrefsView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -124,7 +157,20 @@ func (v *PrefsView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x Prefs
 	var x Prefs
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *PrefsView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x Prefs
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -239,8 +285,17 @@ func (v ServeConfigView) AsStruct() *ServeConfig {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v ServeConfigView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v ServeConfigView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v ServeConfigView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *ServeConfigView) UnmarshalJSON(b []byte) error {
 func (v *ServeConfigView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -249,7 +304,20 @@ func (v *ServeConfigView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x ServeConfig
 	var x ServeConfig
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *ServeConfigView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x ServeConfig
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -323,8 +391,17 @@ func (v ServiceConfigView) AsStruct() *ServiceConfig {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v ServiceConfigView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v ServiceConfigView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
 
 
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v ServiceConfigView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *ServiceConfigView) UnmarshalJSON(b []byte) error {
 func (v *ServiceConfigView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -333,7 +410,20 @@ func (v *ServiceConfigView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x ServiceConfig
 	var x ServiceConfig
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *ServiceConfigView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x ServiceConfig
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -388,8 +478,17 @@ func (v TCPPortHandlerView) AsStruct() *TCPPortHandler {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v TCPPortHandlerView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v TCPPortHandlerView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v TCPPortHandlerView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *TCPPortHandlerView) UnmarshalJSON(b []byte) error {
 func (v *TCPPortHandlerView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -398,7 +497,20 @@ func (v *TCPPortHandlerView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x TCPPortHandler
 	var x TCPPortHandler
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *TCPPortHandlerView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x TCPPortHandler
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -446,8 +558,17 @@ func (v HTTPHandlerView) AsStruct() *HTTPHandler {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v HTTPHandlerView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v HTTPHandlerView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
 
 
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v HTTPHandlerView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *HTTPHandlerView) UnmarshalJSON(b []byte) error {
 func (v *HTTPHandlerView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -456,7 +577,20 @@ func (v *HTTPHandlerView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x HTTPHandler
 	var x HTTPHandler
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *HTTPHandlerView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x HTTPHandler
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -502,8 +636,17 @@ func (v WebServerConfigView) AsStruct() *WebServerConfig {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v WebServerConfigView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v WebServerConfigView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v WebServerConfigView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *WebServerConfigView) UnmarshalJSON(b []byte) error {
 func (v *WebServerConfigView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -512,7 +655,20 @@ func (v *WebServerConfigView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x WebServerConfig
 	var x WebServerConfig
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *WebServerConfigView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x WebServerConfig
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x

+ 483 - 41
tailcfg/tailcfg_view.go

@@ -6,11 +6,13 @@
 package tailcfg
 package tailcfg
 
 
 import (
 import (
-	"encoding/json"
+	jsonv1 "encoding/json"
 	"errors"
 	"errors"
 	"net/netip"
 	"net/netip"
 	"time"
 	"time"
 
 
+	jsonv2 "github.com/go-json-experiment/json"
+	"github.com/go-json-experiment/json/jsontext"
 	"tailscale.com/types/dnstype"
 	"tailscale.com/types/dnstype"
 	"tailscale.com/types/key"
 	"tailscale.com/types/key"
 	"tailscale.com/types/opt"
 	"tailscale.com/types/opt"
@@ -49,8 +51,17 @@ func (v UserView) AsStruct() *User {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v UserView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v UserView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v UserView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *UserView) UnmarshalJSON(b []byte) error {
 func (v *UserView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -59,7 +70,20 @@ func (v *UserView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x User
 	var x User
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *UserView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x User
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -107,8 +131,17 @@ func (v NodeView) AsStruct() *Node {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v NodeView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v NodeView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v NodeView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *NodeView) UnmarshalJSON(b []byte) error {
 func (v *NodeView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -117,7 +150,20 @@ func (v *NodeView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x Node
 	var x Node
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *NodeView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x Node
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -246,8 +292,17 @@ func (v HostinfoView) AsStruct() *Hostinfo {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v HostinfoView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v HostinfoView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v HostinfoView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *HostinfoView) UnmarshalJSON(b []byte) error {
 func (v *HostinfoView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -256,7 +311,20 @@ func (v *HostinfoView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x Hostinfo
 	var x Hostinfo
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *HostinfoView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x Hostinfo
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -380,8 +448,17 @@ func (v NetInfoView) AsStruct() *NetInfo {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v NetInfoView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v NetInfoView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v NetInfoView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *NetInfoView) UnmarshalJSON(b []byte) error {
 func (v *NetInfoView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -390,7 +467,20 @@ func (v *NetInfoView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x NetInfo
 	var x NetInfo
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *NetInfoView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x NetInfo
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -460,8 +550,17 @@ func (v LoginView) AsStruct() *Login {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v LoginView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v LoginView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v LoginView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *LoginView) UnmarshalJSON(b []byte) error {
 func (v *LoginView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -470,7 +569,20 @@ func (v *LoginView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x Login
 	var x Login
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *LoginView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x Login
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -521,8 +633,17 @@ func (v DNSConfigView) AsStruct() *DNSConfig {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v DNSConfigView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v DNSConfigView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v DNSConfigView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *DNSConfigView) UnmarshalJSON(b []byte) error {
 func (v *DNSConfigView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -531,7 +652,20 @@ func (v *DNSConfigView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x DNSConfig
 	var x DNSConfig
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *DNSConfigView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x DNSConfig
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -602,8 +736,17 @@ func (v RegisterResponseView) AsStruct() *RegisterResponse {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v RegisterResponseView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v RegisterResponseView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v RegisterResponseView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *RegisterResponseView) UnmarshalJSON(b []byte) error {
 func (v *RegisterResponseView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -612,7 +755,20 @@ func (v *RegisterResponseView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x RegisterResponse
 	var x RegisterResponse
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *RegisterResponseView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x RegisterResponse
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -668,8 +824,17 @@ func (v RegisterResponseAuthView) AsStruct() *RegisterResponseAuth {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v RegisterResponseAuthView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v RegisterResponseAuthView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v RegisterResponseAuthView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *RegisterResponseAuthView) UnmarshalJSON(b []byte) error {
 func (v *RegisterResponseAuthView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -678,7 +843,20 @@ func (v *RegisterResponseAuthView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x RegisterResponseAuth
 	var x RegisterResponseAuth
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *RegisterResponseAuthView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x RegisterResponseAuth
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -726,8 +904,17 @@ func (v RegisterRequestView) AsStruct() *RegisterRequest {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v RegisterRequestView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v RegisterRequestView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v RegisterRequestView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *RegisterRequestView) UnmarshalJSON(b []byte) error {
 func (v *RegisterRequestView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -736,7 +923,20 @@ func (v *RegisterRequestView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x RegisterRequest
 	var x RegisterRequest
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *RegisterRequestView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x RegisterRequest
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -816,8 +1016,17 @@ func (v DERPHomeParamsView) AsStruct() *DERPHomeParams {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v DERPHomeParamsView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v DERPHomeParamsView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v DERPHomeParamsView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *DERPHomeParamsView) UnmarshalJSON(b []byte) error {
 func (v *DERPHomeParamsView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -826,7 +1035,20 @@ func (v *DERPHomeParamsView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x DERPHomeParams
 	var x DERPHomeParams
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *DERPHomeParamsView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x DERPHomeParams
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -870,8 +1092,17 @@ func (v DERPRegionView) AsStruct() *DERPRegion {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v DERPRegionView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v DERPRegionView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v DERPRegionView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *DERPRegionView) UnmarshalJSON(b []byte) error {
 func (v *DERPRegionView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -880,7 +1111,20 @@ func (v *DERPRegionView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x DERPRegion
 	var x DERPRegion
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *DERPRegionView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x DERPRegion
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -938,8 +1182,17 @@ func (v DERPMapView) AsStruct() *DERPMap {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v DERPMapView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v DERPMapView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v DERPMapView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *DERPMapView) UnmarshalJSON(b []byte) error {
 func (v *DERPMapView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -948,7 +1201,20 @@ func (v *DERPMapView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x DERPMap
 	var x DERPMap
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *DERPMapView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x DERPMap
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -999,8 +1265,17 @@ func (v DERPNodeView) AsStruct() *DERPNode {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v DERPNodeView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v DERPNodeView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v DERPNodeView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *DERPNodeView) UnmarshalJSON(b []byte) error {
 func (v *DERPNodeView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -1009,7 +1284,20 @@ func (v *DERPNodeView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x DERPNode
 	var x DERPNode
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *DERPNodeView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x DERPNode
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -1073,8 +1361,17 @@ func (v SSHRuleView) AsStruct() *SSHRule {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v SSHRuleView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v SSHRuleView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v SSHRuleView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *SSHRuleView) UnmarshalJSON(b []byte) error {
 func (v *SSHRuleView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -1083,7 +1380,20 @@ func (v *SSHRuleView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x SSHRule
 	var x SSHRule
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *SSHRuleView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x SSHRule
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -1139,8 +1449,17 @@ func (v SSHActionView) AsStruct() *SSHAction {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v SSHActionView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v SSHActionView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v SSHActionView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *SSHActionView) UnmarshalJSON(b []byte) error {
 func (v *SSHActionView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -1149,7 +1468,20 @@ func (v *SSHActionView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x SSHAction
 	var x SSHAction
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *SSHActionView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x SSHAction
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -1211,8 +1543,17 @@ func (v SSHPrincipalView) AsStruct() *SSHPrincipal {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v SSHPrincipalView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v SSHPrincipalView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v SSHPrincipalView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *SSHPrincipalView) UnmarshalJSON(b []byte) error {
 func (v *SSHPrincipalView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -1221,7 +1562,20 @@ func (v *SSHPrincipalView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x SSHPrincipal
 	var x SSHPrincipal
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *SSHPrincipalView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x SSHPrincipal
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -1273,8 +1627,17 @@ func (v ControlDialPlanView) AsStruct() *ControlDialPlan {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v ControlDialPlanView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v ControlDialPlanView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v ControlDialPlanView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *ControlDialPlanView) UnmarshalJSON(b []byte) error {
 func (v *ControlDialPlanView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -1283,7 +1646,20 @@ func (v *ControlDialPlanView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x ControlDialPlan
 	var x ControlDialPlan
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *ControlDialPlanView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x ControlDialPlan
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -1327,8 +1703,17 @@ func (v LocationView) AsStruct() *Location {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v LocationView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v LocationView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v LocationView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *LocationView) UnmarshalJSON(b []byte) error {
 func (v *LocationView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -1337,7 +1722,20 @@ func (v *LocationView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x Location
 	var x Location
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *LocationView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x Location
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -1391,8 +1789,17 @@ func (v UserProfileView) AsStruct() *UserProfile {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v UserProfileView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v UserProfileView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v UserProfileView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *UserProfileView) UnmarshalJSON(b []byte) error {
 func (v *UserProfileView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -1401,7 +1808,20 @@ func (v *UserProfileView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x UserProfile
 	var x UserProfile
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *UserProfileView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x UserProfile
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -1450,8 +1870,17 @@ func (v VIPServiceView) AsStruct() *VIPService {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v VIPServiceView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v VIPServiceView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v VIPServiceView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *VIPServiceView) UnmarshalJSON(b []byte) error {
 func (v *VIPServiceView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -1460,7 +1889,20 @@ func (v *VIPServiceView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x VIPService
 	var x VIPService
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *VIPServiceView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x VIPService
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x

+ 27 - 3
types/dnstype/dnstype_view.go

@@ -6,10 +6,12 @@
 package dnstype
 package dnstype
 
 
 import (
 import (
-	"encoding/json"
+	jsonv1 "encoding/json"
 	"errors"
 	"errors"
 	"net/netip"
 	"net/netip"
 
 
+	jsonv2 "github.com/go-json-experiment/json"
+	"github.com/go-json-experiment/json/jsontext"
 	"tailscale.com/types/views"
 	"tailscale.com/types/views"
 )
 )
 
 
@@ -43,8 +45,17 @@ func (v ResolverView) AsStruct() *Resolver {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v ResolverView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v ResolverView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v ResolverView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *ResolverView) UnmarshalJSON(b []byte) error {
 func (v *ResolverView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -53,7 +64,20 @@ func (v *ResolverView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x Resolver
 	var x Resolver
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *ResolverView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x Resolver
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x

+ 27 - 3
types/persist/persist_view.go

@@ -6,9 +6,11 @@
 package persist
 package persist
 
 
 import (
 import (
-	"encoding/json"
+	jsonv1 "encoding/json"
 	"errors"
 	"errors"
 
 
+	jsonv2 "github.com/go-json-experiment/json"
+	"github.com/go-json-experiment/json/jsontext"
 	"tailscale.com/tailcfg"
 	"tailscale.com/tailcfg"
 	"tailscale.com/types/key"
 	"tailscale.com/types/key"
 	"tailscale.com/types/structs"
 	"tailscale.com/types/structs"
@@ -45,8 +47,17 @@ func (v PersistView) AsStruct() *Persist {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v PersistView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v PersistView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v PersistView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *PersistView) UnmarshalJSON(b []byte) error {
 func (v *PersistView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -55,7 +66,20 @@ func (v *PersistView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x Persist
 	var x Persist
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *PersistView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x Persist
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x

+ 75 - 7
types/prefs/prefs_example/prefs_example_view.go

@@ -6,10 +6,12 @@
 package prefs_example
 package prefs_example
 
 
 import (
 import (
-	"encoding/json"
+	jsonv1 "encoding/json"
 	"errors"
 	"errors"
 	"net/netip"
 	"net/netip"
 
 
+	jsonv2 "github.com/go-json-experiment/json"
+	"github.com/go-json-experiment/json/jsontext"
 	"tailscale.com/drive"
 	"tailscale.com/drive"
 	"tailscale.com/tailcfg"
 	"tailscale.com/tailcfg"
 	"tailscale.com/types/opt"
 	"tailscale.com/types/opt"
@@ -48,8 +50,17 @@ func (v PrefsView) AsStruct() *Prefs {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v PrefsView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v PrefsView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v PrefsView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *PrefsView) UnmarshalJSON(b []byte) error {
 func (v *PrefsView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -58,7 +69,20 @@ func (v *PrefsView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x Prefs
 	var x Prefs
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *PrefsView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x Prefs
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -160,8 +184,17 @@ func (v AutoUpdatePrefsView) AsStruct() *AutoUpdatePrefs {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v AutoUpdatePrefsView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v AutoUpdatePrefsView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v AutoUpdatePrefsView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *AutoUpdatePrefsView) UnmarshalJSON(b []byte) error {
 func (v *AutoUpdatePrefsView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -170,7 +203,20 @@ func (v *AutoUpdatePrefsView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x AutoUpdatePrefs
 	var x AutoUpdatePrefs
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *AutoUpdatePrefsView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x AutoUpdatePrefs
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -214,8 +260,17 @@ func (v AppConnectorPrefsView) AsStruct() *AppConnectorPrefs {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v AppConnectorPrefsView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v AppConnectorPrefsView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
 
 
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v AppConnectorPrefsView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *AppConnectorPrefsView) UnmarshalJSON(b []byte) error {
 func (v *AppConnectorPrefsView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -224,7 +279,20 @@ func (v *AppConnectorPrefsView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x AppConnectorPrefs
 	var x AppConnectorPrefs
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *AppConnectorPrefsView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x AppConnectorPrefs
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x

+ 124 - 11
types/prefs/prefs_view_test.go

@@ -6,9 +6,12 @@
 package prefs
 package prefs
 
 
 import (
 import (
-	"encoding/json"
+	jsonv1 "encoding/json"
 	"errors"
 	"errors"
 	"net/netip"
 	"net/netip"
+
+	jsonv2 "github.com/go-json-experiment/json"
+	"github.com/go-json-experiment/json/jsontext"
 )
 )
 
 
 //go:generate go run tailscale.com/cmd/cloner  -clonefunc=false -type=TestPrefs,TestBundle,TestValueStruct,TestGenericStruct,TestPrefsGroup -tags=test
 //go:generate go run tailscale.com/cmd/cloner  -clonefunc=false -type=TestPrefs,TestBundle,TestValueStruct,TestGenericStruct,TestPrefsGroup -tags=test
@@ -41,8 +44,17 @@ func (v TestPrefsView) AsStruct() *TestPrefs {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v TestPrefsView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v TestPrefsView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
 
 
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v TestPrefsView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *TestPrefsView) UnmarshalJSON(b []byte) error {
 func (v *TestPrefsView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -51,7 +63,20 @@ func (v *TestPrefsView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x TestPrefs
 	var x TestPrefs
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *TestPrefsView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x TestPrefs
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -145,8 +170,17 @@ func (v TestBundleView) AsStruct() *TestBundle {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v TestBundleView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v TestBundleView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v TestBundleView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *TestBundleView) UnmarshalJSON(b []byte) error {
 func (v *TestBundleView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -155,7 +189,20 @@ func (v *TestBundleView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x TestBundle
 	var x TestBundle
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *TestBundleView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x TestBundle
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -200,8 +247,17 @@ func (v TestValueStructView) AsStruct() *TestValueStruct {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v TestValueStructView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v TestValueStructView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v TestValueStructView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *TestValueStructView) UnmarshalJSON(b []byte) error {
 func (v *TestValueStructView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -210,7 +266,20 @@ func (v *TestValueStructView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x TestValueStruct
 	var x TestValueStruct
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *TestValueStructView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x TestValueStruct
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -253,8 +322,17 @@ func (v TestGenericStructView[T]) AsStruct() *TestGenericStruct[T] {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v TestGenericStructView[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v TestGenericStructView[T]) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
 
 
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v TestGenericStructView[T]) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *TestGenericStructView[T]) UnmarshalJSON(b []byte) error {
 func (v *TestGenericStructView[T]) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -263,7 +341,20 @@ func (v *TestGenericStructView[T]) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x TestGenericStruct[T]
 	var x TestGenericStruct[T]
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *TestGenericStructView[T]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x TestGenericStruct[T]
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x
@@ -308,8 +399,17 @@ func (v TestPrefsGroupView) AsStruct() *TestPrefsGroup {
 	return v.ж.Clone()
 	return v.ж.Clone()
 }
 }
 
 
-func (v TestPrefsGroupView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v TestPrefsGroupView) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v TestPrefsGroupView) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 func (v *TestPrefsGroupView) UnmarshalJSON(b []byte) error {
 func (v *TestPrefsGroupView) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
@@ -318,7 +418,20 @@ func (v *TestPrefsGroupView) UnmarshalJSON(b []byte) error {
 		return nil
 		return nil
 	}
 	}
 	var x TestPrefsGroup
 	var x TestPrefsGroup
-	if err := json.Unmarshal(b, &x); err != nil {
+	if err := jsonv1.Unmarshal(b, &x); err != nil {
+		return err
+	}
+	v.ж = &x
+	return nil
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (v *TestPrefsGroupView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	var x TestPrefsGroup
+	if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
 		return err
 		return err
 	}
 	}
 	v.ж = &x
 	v.ж = &x

+ 131 - 35
types/views/views.go

@@ -7,7 +7,7 @@ package views
 
 
 import (
 import (
 	"bytes"
 	"bytes"
-	"encoding/json"
+	jsonv1 "encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"iter"
 	"iter"
@@ -15,20 +15,12 @@ import (
 	"reflect"
 	"reflect"
 	"slices"
 	"slices"
 
 
+	jsonv2 "github.com/go-json-experiment/json"
+	"github.com/go-json-experiment/json/jsontext"
 	"go4.org/mem"
 	"go4.org/mem"
 	"tailscale.com/types/ptr"
 	"tailscale.com/types/ptr"
 )
 )
 
 
-func unmarshalSliceFromJSON[T any](b []byte, x *[]T) error {
-	if *x != nil {
-		return errors.New("already initialized")
-	}
-	if len(b) == 0 {
-		return nil
-	}
-	return json.Unmarshal(b, x)
-}
-
 // ByteSlice is a read-only accessor for types that are backed by a []byte.
 // ByteSlice is a read-only accessor for types that are backed by a []byte.
 type ByteSlice[T ~[]byte] struct {
 type ByteSlice[T ~[]byte] struct {
 	// ж is the underlying mutable value, named with a hard-to-type
 	// ж is the underlying mutable value, named with a hard-to-type
@@ -93,15 +85,32 @@ func (v ByteSlice[T]) SliceTo(i int) ByteSlice[T] { return ByteSlice[T]{v.ж[:i]
 // Slice returns v[i:j]
 // Slice returns v[i:j]
 func (v ByteSlice[T]) Slice(i, j int) ByteSlice[T] { return ByteSlice[T]{v.ж[i:j]} }
 func (v ByteSlice[T]) Slice(i, j int) ByteSlice[T] { return ByteSlice[T]{v.ж[i:j]} }
 
 
-// MarshalJSON implements json.Marshaler.
-func (v ByteSlice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v ByteSlice[T]) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
 
 
-// UnmarshalJSON implements json.Unmarshaler.
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v ByteSlice[T]) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
+// It must only be called on an uninitialized ByteSlice.
 func (v *ByteSlice[T]) UnmarshalJSON(b []byte) error {
 func (v *ByteSlice[T]) UnmarshalJSON(b []byte) error {
 	if v.ж != nil {
 	if v.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
 	}
 	}
-	return json.Unmarshal(b, &v.ж)
+	return jsonv1.Unmarshal(b, &v.ж)
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+// It must only be called on an uninitialized ByteSlice.
+func (v *ByteSlice[T]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	return jsonv2.UnmarshalDecode(dec, &v.ж)
 }
 }
 
 
 // StructView represents the corresponding StructView of a Viewable. The concrete types are
 // StructView represents the corresponding StructView of a Viewable. The concrete types are
@@ -159,11 +168,35 @@ func (v SliceView[T, V]) All() iter.Seq2[int, V] {
 	}
 	}
 }
 }
 
 
-// MarshalJSON implements json.Marshaler.
-func (v SliceView[T, V]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
+// MarshalJSON implements [jsonv1.Marshaler].
+func (v SliceView[T, V]) MarshalJSON() ([]byte, error) {
+	return jsonv1.Marshal(v.ж)
+}
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v SliceView[T, V]) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
 
 
-// UnmarshalJSON implements json.Unmarshaler.
-func (v *SliceView[T, V]) UnmarshalJSON(b []byte) error { return unmarshalSliceFromJSON(b, &v.ж) }
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
+// It must only be called on an uninitialized SliceView.
+func (v *SliceView[T, V]) UnmarshalJSON(b []byte) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	} else if len(b) == 0 {
+		return nil
+	}
+	return jsonv1.Unmarshal(b, &v.ж)
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+// It must only be called on an uninitialized SliceView.
+func (v *SliceView[T, V]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	return jsonv2.UnmarshalDecode(dec, &v.ж)
+}
 
 
 // IsNil reports whether the underlying slice is nil.
 // IsNil reports whether the underlying slice is nil.
 func (v SliceView[T, V]) IsNil() bool { return v.ж == nil }
 func (v SliceView[T, V]) IsNil() bool { return v.ж == nil }
@@ -252,14 +285,34 @@ func SliceOf[T any](x []T) Slice[T] {
 	return Slice[T]{x}
 	return Slice[T]{x}
 }
 }
 
 
-// MarshalJSON implements json.Marshaler.
+// MarshalJSON implements [jsonv1.Marshaler].
 func (v Slice[T]) MarshalJSON() ([]byte, error) {
 func (v Slice[T]) MarshalJSON() ([]byte, error) {
-	return json.Marshal(v.ж)
+	return jsonv1.Marshal(v.ж)
 }
 }
 
 
-// UnmarshalJSON implements json.Unmarshaler.
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (v Slice[T]) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, v.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
+// It must only be called on an uninitialized Slice.
 func (v *Slice[T]) UnmarshalJSON(b []byte) error {
 func (v *Slice[T]) UnmarshalJSON(b []byte) error {
-	return unmarshalSliceFromJSON(b, &v.ж)
+	if v.ж != nil {
+		return errors.New("already initialized")
+	} else if len(b) == 0 {
+		return nil
+	}
+	return jsonv1.Unmarshal(b, &v.ж)
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+// It must only be called on an uninitialized Slice.
+func (v *Slice[T]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if v.ж != nil {
+		return errors.New("already initialized")
+	}
+	return jsonv2.UnmarshalDecode(dec, &v.ж)
 }
 }
 
 
 // IsNil reports whether the underlying slice is nil.
 // IsNil reports whether the underlying slice is nil.
@@ -512,18 +565,32 @@ func (m MapSlice[K, V]) GetOk(k K) (Slice[V], bool) {
 	return SliceOf(v), ok
 	return SliceOf(v), ok
 }
 }
 
 
-// MarshalJSON implements json.Marshaler.
+// MarshalJSON implements [jsonv1.Marshaler].
 func (m MapSlice[K, V]) MarshalJSON() ([]byte, error) {
 func (m MapSlice[K, V]) MarshalJSON() ([]byte, error) {
-	return json.Marshal(m.ж)
+	return jsonv1.Marshal(m.ж)
 }
 }
 
 
-// UnmarshalJSON implements json.Unmarshaler.
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (m MapSlice[K, V]) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, m.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 // It should only be called on an uninitialized Map.
 // It should only be called on an uninitialized Map.
 func (m *MapSlice[K, V]) UnmarshalJSON(b []byte) error {
 func (m *MapSlice[K, V]) UnmarshalJSON(b []byte) error {
 	if m.ж != nil {
 	if m.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
 	}
 	}
-	return json.Unmarshal(b, &m.ж)
+	return jsonv1.Unmarshal(b, &m.ж)
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+// It should only be called on an uninitialized MapSlice.
+func (m *MapSlice[K, V]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if m.ж != nil {
+		return errors.New("already initialized")
+	}
+	return jsonv2.UnmarshalDecode(dec, &m.ж)
 }
 }
 
 
 // AsMap returns a shallow-clone of the underlying map.
 // AsMap returns a shallow-clone of the underlying map.
@@ -600,18 +667,32 @@ func (m Map[K, V]) GetOk(k K) (V, bool) {
 	return v, ok
 	return v, ok
 }
 }
 
 
-// MarshalJSON implements json.Marshaler.
+// MarshalJSON implements [jsonv1.Marshaler].
 func (m Map[K, V]) MarshalJSON() ([]byte, error) {
 func (m Map[K, V]) MarshalJSON() ([]byte, error) {
-	return json.Marshal(m.ж)
+	return jsonv1.Marshal(m.ж)
 }
 }
 
 
-// UnmarshalJSON implements json.Unmarshaler.
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (m Map[K, V]) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, m.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
 // It should only be called on an uninitialized Map.
 // It should only be called on an uninitialized Map.
 func (m *Map[K, V]) UnmarshalJSON(b []byte) error {
 func (m *Map[K, V]) UnmarshalJSON(b []byte) error {
 	if m.ж != nil {
 	if m.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
 	}
 	}
-	return json.Unmarshal(b, &m.ж)
+	return jsonv1.Unmarshal(b, &m.ж)
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+// It must only be called on an uninitialized Map.
+func (m *Map[K, V]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if m.ж != nil {
+		return errors.New("already initialized")
+	}
+	return jsonv2.UnmarshalDecode(dec, &m.ж)
 }
 }
 
 
 // AsMap returns a shallow-clone of the underlying map.
 // AsMap returns a shallow-clone of the underlying map.
@@ -809,17 +890,32 @@ func ValuePointerOf[T any](v *T) ValuePointer[T] {
 	return ValuePointer[T]{v}
 	return ValuePointer[T]{v}
 }
 }
 
 
-// MarshalJSON implements [json.Marshaler].
+// MarshalJSON implements [jsonv1.Marshaler].
 func (p ValuePointer[T]) MarshalJSON() ([]byte, error) {
 func (p ValuePointer[T]) MarshalJSON() ([]byte, error) {
-	return json.Marshal(p.ж)
+	return jsonv1.Marshal(p.ж)
 }
 }
 
 
-// UnmarshalJSON implements [json.Unmarshaler].
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (p ValuePointer[T]) MarshalJSONTo(enc *jsontext.Encoder) error {
+	return jsonv2.MarshalEncode(enc, p.ж)
+}
+
+// UnmarshalJSON implements [jsonv1.Unmarshaler].
+// It must only be called on an uninitialized ValuePointer.
 func (p *ValuePointer[T]) UnmarshalJSON(b []byte) error {
 func (p *ValuePointer[T]) UnmarshalJSON(b []byte) error {
 	if p.ж != nil {
 	if p.ж != nil {
 		return errors.New("already initialized")
 		return errors.New("already initialized")
 	}
 	}
-	return json.Unmarshal(b, &p.ж)
+	return jsonv1.Unmarshal(b, &p.ж)
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+// It must only be called on an uninitialized ValuePointer.
+func (p *ValuePointer[T]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+	if p.ж != nil {
+		return errors.New("already initialized")
+	}
+	return jsonv2.UnmarshalDecode(dec, &p.ж)
 }
 }
 
 
 // ContainsPointers reports whether T contains any pointers,
 // ContainsPointers reports whether T contains any pointers,

+ 65 - 27
types/views/views_test.go

@@ -4,8 +4,7 @@
 package views
 package views
 
 
 import (
 import (
-	"bytes"
-	"encoding/json"
+	jsonv1 "encoding/json"
 	"fmt"
 	"fmt"
 	"net/netip"
 	"net/netip"
 	"reflect"
 	"reflect"
@@ -15,9 +14,27 @@ import (
 	"unsafe"
 	"unsafe"
 
 
 	qt "github.com/frankban/quicktest"
 	qt "github.com/frankban/quicktest"
+	jsonv2 "github.com/go-json-experiment/json"
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
 	"tailscale.com/types/structs"
 	"tailscale.com/types/structs"
 )
 )
 
 
+// Statically verify that each type implements the following interfaces.
+var _ = []interface {
+	jsonv1.Marshaler
+	jsonv1.Unmarshaler
+	jsonv2.MarshalerTo
+	jsonv2.UnmarshalerFrom
+}{
+	(*ByteSlice[[]byte])(nil),
+	(*SliceView[*testStruct, testStructView])(nil),
+	(*Slice[testStruct])(nil),
+	(*MapSlice[*testStruct, testStructView])(nil),
+	(*Map[*testStruct, testStructView])(nil),
+	(*ValuePointer[testStruct])(nil),
+}
+
 type viewStruct struct {
 type viewStruct struct {
 	Int        int
 	Int        int
 	Addrs      Slice[netip.Prefix]
 	Addrs      Slice[netip.Prefix]
@@ -83,14 +100,16 @@ func TestViewsJSON(t *testing.T) {
 	ipp := SliceOf(mustCIDR("192.168.0.0/24"))
 	ipp := SliceOf(mustCIDR("192.168.0.0/24"))
 	ss := SliceOf([]string{"bar"})
 	ss := SliceOf([]string{"bar"})
 	tests := []struct {
 	tests := []struct {
-		name     string
-		in       viewStruct
-		wantJSON string
+		name       string
+		in         viewStruct
+		wantJSONv1 string
+		wantJSONv2 string
 	}{
 	}{
 		{
 		{
-			name:     "empty",
-			in:       viewStruct{},
-			wantJSON: `{"Int":0,"Addrs":null,"Strings":null}`,
+			name:       "empty",
+			in:         viewStruct{},
+			wantJSONv1: `{"Int":0,"Addrs":null,"Strings":null}`,
+			wantJSONv2: `{"Int":0,"Addrs":[],"Strings":[]}`,
 		},
 		},
 		{
 		{
 			name: "everything",
 			name: "everything",
@@ -101,30 +120,49 @@ func TestViewsJSON(t *testing.T) {
 				StringsPtr: &ss,
 				StringsPtr: &ss,
 				Strings:    ss,
 				Strings:    ss,
 			},
 			},
-			wantJSON: `{"Int":1234,"Addrs":["192.168.0.0/24"],"Strings":["bar"],"AddrsPtr":["192.168.0.0/24"],"StringsPtr":["bar"]}`,
+			wantJSONv1: `{"Int":1234,"Addrs":["192.168.0.0/24"],"Strings":["bar"],"AddrsPtr":["192.168.0.0/24"],"StringsPtr":["bar"]}`,
+			wantJSONv2: `{"Int":1234,"Addrs":["192.168.0.0/24"],"Strings":["bar"],"AddrsPtr":["192.168.0.0/24"],"StringsPtr":["bar"]}`,
 		},
 		},
 	}
 	}
 
 
-	var buf bytes.Buffer
-	encoder := json.NewEncoder(&buf)
-	encoder.SetIndent("", "")
 	for _, tc := range tests {
 	for _, tc := range tests {
-		buf.Reset()
-		if err := encoder.Encode(&tc.in); err != nil {
-			t.Fatal(err)
-		}
-		b := buf.Bytes()
-		gotJSON := strings.TrimSpace(string(b))
-		if tc.wantJSON != gotJSON {
-			t.Fatalf("JSON: %v; want: %v", gotJSON, tc.wantJSON)
-		}
-		var got viewStruct
-		if err := json.Unmarshal(b, &got); err != nil {
-			t.Fatal(err)
-		}
-		if !reflect.DeepEqual(got, tc.in) {
-			t.Fatalf("unmarshal resulted in different output: %+v; want %+v", got, tc.in)
+		cmpOpts := cmp.Options{
+			cmp.AllowUnexported(Slice[string]{}),
+			cmp.AllowUnexported(Slice[netip.Prefix]{}),
+			cmpopts.EquateComparable(netip.Prefix{}),
 		}
 		}
+		t.Run("JSONv1", func(t *testing.T) {
+			gotJSON, err := jsonv1.Marshal(tc.in)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if string(gotJSON) != tc.wantJSONv1 {
+				t.Fatalf("JSON: %s; want: %s", gotJSON, tc.wantJSONv1)
+			}
+			var got viewStruct
+			if err := jsonv1.Unmarshal(gotJSON, &got); err != nil {
+				t.Fatal(err)
+			}
+			if d := cmp.Diff(got, tc.in, cmpOpts); d != "" {
+				t.Fatalf("unmarshal mismatch (-got +want):\n%s", d)
+			}
+		})
+		t.Run("JSONv2", func(t *testing.T) {
+			gotJSON, err := jsonv2.Marshal(tc.in)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if string(gotJSON) != tc.wantJSONv2 {
+				t.Fatalf("JSON: %s; want: %s", gotJSON, tc.wantJSONv2)
+			}
+			var got viewStruct
+			if err := jsonv2.Unmarshal(gotJSON, &got); err != nil {
+				t.Fatal(err)
+			}
+			if d := cmp.Diff(got, tc.in, cmpOpts, cmpopts.EquateEmpty()); d != "" {
+				t.Fatalf("unmarshal mismatch (-got +want):\n%s", d)
+			}
+		})
 	}
 	}
 }
 }
 
 

+ 20 - 9
util/codegen/codegen.go

@@ -85,28 +85,35 @@ func NewImportTracker(thisPkg *types.Package) *ImportTracker {
 	}
 	}
 }
 }
 
 
+type namePkgPath struct {
+	name    string // optional import name
+	pkgPath string
+}
+
 // ImportTracker provides a mechanism to track and build import paths.
 // ImportTracker provides a mechanism to track and build import paths.
 type ImportTracker struct {
 type ImportTracker struct {
 	thisPkg  *types.Package
 	thisPkg  *types.Package
-	packages map[string]bool
+	packages map[namePkgPath]bool
 }
 }
 
 
-func (it *ImportTracker) Import(pkg string) {
-	if pkg != "" && !it.packages[pkg] {
-		mak.Set(&it.packages, pkg, true)
+// Import imports pkgPath under an optional import name.
+func (it *ImportTracker) Import(name, pkgPath string) {
+	if pkgPath != "" && !it.packages[namePkgPath{name, pkgPath}] {
+		mak.Set(&it.packages, namePkgPath{name, pkgPath}, true)
 	}
 	}
 }
 }
 
 
-// Has reports whether the specified package has been imported.
-func (it *ImportTracker) Has(pkg string) bool {
-	return it.packages[pkg]
+// Has reports whether the specified package path has been imported
+// under the particular import name.
+func (it *ImportTracker) Has(name, pkgPath string) bool {
+	return it.packages[namePkgPath{name, pkgPath}]
 }
 }
 
 
 func (it *ImportTracker) qualifier(pkg *types.Package) string {
 func (it *ImportTracker) qualifier(pkg *types.Package) string {
 	if it.thisPkg == pkg {
 	if it.thisPkg == pkg {
 		return ""
 		return ""
 	}
 	}
-	it.Import(pkg.Path())
+	it.Import("", pkg.Path())
 	// TODO(maisem): handle conflicts?
 	// TODO(maisem): handle conflicts?
 	return pkg.Name()
 	return pkg.Name()
 }
 }
@@ -128,7 +135,11 @@ func (it *ImportTracker) PackagePrefix(pkg *types.Package) string {
 func (it *ImportTracker) Write(w io.Writer) {
 func (it *ImportTracker) Write(w io.Writer) {
 	fmt.Fprintf(w, "import (\n")
 	fmt.Fprintf(w, "import (\n")
 	for s := range it.packages {
 	for s := range it.packages {
-		fmt.Fprintf(w, "\t%q\n", s)
+		if s.name == "" {
+			fmt.Fprintf(w, "\t%q\n", s.pkgPath)
+		} else {
+			fmt.Fprintf(w, "\t%s	%q\n", s.name, s.pkgPath)
+		}
 	}
 	}
 	fmt.Fprintf(w, ")\n\n")
 	fmt.Fprintf(w, ")\n\n")
 }
 }