Source file src/encoding/json/v2/example_test.go

     1  // Copyright 2022 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build goexperiment.jsonv2
     6  
     7  package json_test
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"fmt"
    13  	"log"
    14  	"math"
    15  	"net/http"
    16  	"net/netip"
    17  	"os"
    18  	"reflect"
    19  	"strconv"
    20  	"strings"
    21  	"sync/atomic"
    22  	"time"
    23  
    24  	"encoding/json/jsontext"
    25  	"encoding/json/v2"
    26  )
    27  
    28  // If a type implements [encoding.TextMarshaler] and/or [encoding.TextUnmarshaler],
    29  // then the MarshalText and UnmarshalText methods are used to encode/decode
    30  // the value to/from a JSON string.
    31  func Example_textMarshal() {
    32  	// Round-trip marshal and unmarshal a hostname map where the netip.Addr type
    33  	// implements both encoding.TextMarshaler and encoding.TextUnmarshaler.
    34  	want := map[netip.Addr]string{
    35  		netip.MustParseAddr("192.168.0.100"): "carbonite",
    36  		netip.MustParseAddr("192.168.0.101"): "obsidian",
    37  		netip.MustParseAddr("192.168.0.102"): "diamond",
    38  	}
    39  	b, err := json.Marshal(&want, json.Deterministic(true))
    40  	if err != nil {
    41  		log.Fatal(err)
    42  	}
    43  	var got map[netip.Addr]string
    44  	err = json.Unmarshal(b, &got)
    45  	if err != nil {
    46  		log.Fatal(err)
    47  	}
    48  
    49  	// Sanity check.
    50  	if !reflect.DeepEqual(got, want) {
    51  		log.Fatalf("roundtrip mismatch: got %v, want %v", got, want)
    52  	}
    53  
    54  	// Print the serialized JSON object.
    55  	(*jsontext.Value)(&b).Indent() // indent for readability
    56  	fmt.Println(string(b))
    57  
    58  	// Output:
    59  	// {
    60  	// 	"192.168.0.100": "carbonite",
    61  	// 	"192.168.0.101": "obsidian",
    62  	// 	"192.168.0.102": "diamond"
    63  	// }
    64  }
    65  
    66  // By default, JSON object names for Go struct fields are derived from
    67  // the Go field name, but may be specified in the `json` tag.
    68  // Due to JSON's heritage in JavaScript, the most common naming convention
    69  // used for JSON object names is camelCase.
    70  func Example_fieldNames() {
    71  	var value struct {
    72  		// This field is explicitly ignored with the special "-" name.
    73  		Ignored any `json:"-"`
    74  		// No JSON name is not provided, so the Go field name is used.
    75  		GoName any
    76  		// A JSON name is provided without any special characters.
    77  		JSONName any `json:"jsonName"`
    78  		// No JSON name is not provided, so the Go field name is used.
    79  		Option any `json:",case:ignore"`
    80  		// An empty JSON name specified using an single-quoted string literal.
    81  		Empty any `json:"''"`
    82  		// A dash JSON name specified using an single-quoted string literal.
    83  		Dash any `json:"'-'"`
    84  		// A comma JSON name specified using an single-quoted string literal.
    85  		Comma any `json:"','"`
    86  		// JSON name with quotes specified using a single-quoted string literal.
    87  		Quote any `json:"'\"\\''"`
    88  		// An unexported field is always ignored.
    89  		unexported any
    90  	}
    91  
    92  	b, err := json.Marshal(value)
    93  	if err != nil {
    94  		log.Fatal(err)
    95  	}
    96  	(*jsontext.Value)(&b).Indent() // indent for readability
    97  	fmt.Println(string(b))
    98  
    99  	// Output:
   100  	// {
   101  	// 	"GoName": null,
   102  	// 	"jsonName": null,
   103  	// 	"Option": null,
   104  	// 	"": null,
   105  	// 	"-": null,
   106  	// 	",": null,
   107  	// 	"\"'": null
   108  	// }
   109  }
   110  
   111  // Unmarshal matches JSON object names with Go struct fields using
   112  // a case-sensitive match, but can be configured to use a case-insensitive
   113  // match with the "case:ignore" option. This permits unmarshaling from inputs
   114  // that use naming conventions such as camelCase, snake_case, or kebab-case.
   115  func Example_caseSensitivity() {
   116  	// JSON input using various naming conventions.
   117  	const input = `[
   118  		{"firstname": true},
   119  		{"firstName": true},
   120  		{"FirstName": true},
   121  		{"FIRSTNAME": true},
   122  		{"first_name": true},
   123  		{"FIRST_NAME": true},
   124  		{"first-name": true},
   125  		{"FIRST-NAME": true},
   126  		{"unknown": true}
   127  	]`
   128  
   129  	// Without "case:ignore", Unmarshal looks for an exact match.
   130  	var caseStrict []struct {
   131  		X bool `json:"firstName"`
   132  	}
   133  	if err := json.Unmarshal([]byte(input), &caseStrict); err != nil {
   134  		log.Fatal(err)
   135  	}
   136  	fmt.Println(caseStrict) // exactly 1 match found
   137  
   138  	// With "case:ignore", Unmarshal looks first for an exact match,
   139  	// then for a case-insensitive match if none found.
   140  	var caseIgnore []struct {
   141  		X bool `json:"firstName,case:ignore"`
   142  	}
   143  	if err := json.Unmarshal([]byte(input), &caseIgnore); err != nil {
   144  		log.Fatal(err)
   145  	}
   146  	fmt.Println(caseIgnore) // 8 matches found
   147  
   148  	// Output:
   149  	// [{false} {true} {false} {false} {false} {false} {false} {false} {false}]
   150  	// [{true} {true} {true} {true} {true} {true} {true} {true} {false}]
   151  }
   152  
   153  // Go struct fields can be omitted from the output depending on either
   154  // the input Go value or the output JSON encoding of the value.
   155  // The "omitzero" option omits a field if it is the zero Go value or
   156  // implements a "IsZero() bool" method that reports true.
   157  // The "omitempty" option omits a field if it encodes as an empty JSON value,
   158  // which we define as a JSON null or empty JSON string, object, or array.
   159  // In many cases, the behavior of "omitzero" and "omitempty" are equivalent.
   160  // If both provide the desired effect, then using "omitzero" is preferred.
   161  func Example_omitFields() {
   162  	type MyStruct struct {
   163  		Foo string `json:",omitzero"`
   164  		Bar []int  `json:",omitempty"`
   165  		// Both "omitzero" and "omitempty" can be specified together,
   166  		// in which case the field is omitted if either would take effect.
   167  		// This omits the Baz field either if it is a nil pointer or
   168  		// if it would have encoded as an empty JSON object.
   169  		Baz *MyStruct `json:",omitzero,omitempty"`
   170  	}
   171  
   172  	// Demonstrate behavior of "omitzero".
   173  	b, err := json.Marshal(struct {
   174  		Bool         bool        `json:",omitzero"`
   175  		Int          int         `json:",omitzero"`
   176  		String       string      `json:",omitzero"`
   177  		Time         time.Time   `json:",omitzero"`
   178  		Addr         netip.Addr  `json:",omitzero"`
   179  		Struct       MyStruct    `json:",omitzero"`
   180  		SliceNil     []int       `json:",omitzero"`
   181  		Slice        []int       `json:",omitzero"`
   182  		MapNil       map[int]int `json:",omitzero"`
   183  		Map          map[int]int `json:",omitzero"`
   184  		PointerNil   *string     `json:",omitzero"`
   185  		Pointer      *string     `json:",omitzero"`
   186  		InterfaceNil any         `json:",omitzero"`
   187  		Interface    any         `json:",omitzero"`
   188  	}{
   189  		// Bool is omitted since false is the zero value for a Go bool.
   190  		Bool: false,
   191  		// Int is omitted since 0 is the zero value for a Go int.
   192  		Int: 0,
   193  		// String is omitted since "" is the zero value for a Go string.
   194  		String: "",
   195  		// Time is omitted since time.Time.IsZero reports true.
   196  		Time: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
   197  		// Addr is omitted since netip.Addr{} is the zero value for a Go struct.
   198  		Addr: netip.Addr{},
   199  		// Struct is NOT omitted since it is not the zero value for a Go struct.
   200  		Struct: MyStruct{Bar: []int{}, Baz: new(MyStruct)},
   201  		// SliceNil is omitted since nil is the zero value for a Go slice.
   202  		SliceNil: nil,
   203  		// Slice is NOT omitted since []int{} is not the zero value for a Go slice.
   204  		Slice: []int{},
   205  		// MapNil is omitted since nil is the zero value for a Go map.
   206  		MapNil: nil,
   207  		// Map is NOT omitted since map[int]int{} is not the zero value for a Go map.
   208  		Map: map[int]int{},
   209  		// PointerNil is omitted since nil is the zero value for a Go pointer.
   210  		PointerNil: nil,
   211  		// Pointer is NOT omitted since new(string) is not the zero value for a Go pointer.
   212  		Pointer: new(string),
   213  		// InterfaceNil is omitted since nil is the zero value for a Go interface.
   214  		InterfaceNil: nil,
   215  		// Interface is NOT omitted since (*string)(nil) is not the zero value for a Go interface.
   216  		Interface: (*string)(nil),
   217  	})
   218  	if err != nil {
   219  		log.Fatal(err)
   220  	}
   221  	(*jsontext.Value)(&b).Indent()      // indent for readability
   222  	fmt.Println("OmitZero:", string(b)) // outputs "Struct", "Slice", "Map", "Pointer", and "Interface"
   223  
   224  	// Demonstrate behavior of "omitempty".
   225  	b, err = json.Marshal(struct {
   226  		Bool         bool        `json:",omitempty"`
   227  		Int          int         `json:",omitempty"`
   228  		String       string      `json:",omitempty"`
   229  		Time         time.Time   `json:",omitempty"`
   230  		Addr         netip.Addr  `json:",omitempty"`
   231  		Struct       MyStruct    `json:",omitempty"`
   232  		Slice        []int       `json:",omitempty"`
   233  		Map          map[int]int `json:",omitempty"`
   234  		PointerNil   *string     `json:",omitempty"`
   235  		Pointer      *string     `json:",omitempty"`
   236  		InterfaceNil any         `json:",omitempty"`
   237  		Interface    any         `json:",omitempty"`
   238  	}{
   239  		// Bool is NOT omitted since false is not an empty JSON value.
   240  		Bool: false,
   241  		// Int is NOT omitted since 0 is not a empty JSON value.
   242  		Int: 0,
   243  		// String is omitted since "" is an empty JSON string.
   244  		String: "",
   245  		// Time is NOT omitted since this encodes as a non-empty JSON string.
   246  		Time: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
   247  		// Addr is omitted since this encodes as an empty JSON string.
   248  		Addr: netip.Addr{},
   249  		// Struct is omitted since {} is an empty JSON object.
   250  		Struct: MyStruct{Bar: []int{}, Baz: new(MyStruct)},
   251  		// Slice is omitted since [] is an empty JSON array.
   252  		Slice: []int{},
   253  		// Map is omitted since {} is an empty JSON object.
   254  		Map: map[int]int{},
   255  		// PointerNil is omitted since null is an empty JSON value.
   256  		PointerNil: nil,
   257  		// Pointer is omitted since "" is an empty JSON string.
   258  		Pointer: new(string),
   259  		// InterfaceNil is omitted since null is an empty JSON value.
   260  		InterfaceNil: nil,
   261  		// Interface is omitted since null is an empty JSON value.
   262  		Interface: (*string)(nil),
   263  	})
   264  	if err != nil {
   265  		log.Fatal(err)
   266  	}
   267  	(*jsontext.Value)(&b).Indent()       // indent for readability
   268  	fmt.Println("OmitEmpty:", string(b)) // outputs "Bool", "Int", and "Time"
   269  
   270  	// Output:
   271  	// OmitZero: {
   272  	// 	"Struct": {},
   273  	// 	"Slice": [],
   274  	// 	"Map": {},
   275  	// 	"Pointer": "",
   276  	// 	"Interface": null
   277  	// }
   278  	// OmitEmpty: {
   279  	// 	"Bool": false,
   280  	// 	"Int": 0,
   281  	// 	"Time": "0001-01-01T00:00:00Z"
   282  	// }
   283  }
   284  
   285  // JSON objects can be inlined within a parent object similar to
   286  // how Go structs can be embedded within a parent struct.
   287  // The inlining rules are similar to those of Go embedding,
   288  // but operates upon the JSON namespace.
   289  func Example_inlinedFields() {
   290  	// Base is embedded within Container.
   291  	type Base struct {
   292  		// ID is promoted into the JSON object for Container.
   293  		ID string
   294  		// Type is ignored due to presence of Container.Type.
   295  		Type string
   296  		// Time cancels out with Container.Inlined.Time.
   297  		Time time.Time
   298  	}
   299  	// Other is embedded within Container.
   300  	type Other struct{ Cost float64 }
   301  	// Container embeds Base and Other.
   302  	type Container struct {
   303  		// Base is an embedded struct and is implicitly JSON inlined.
   304  		Base
   305  		// Type takes precedence over Base.Type.
   306  		Type int
   307  		// Inlined is a named Go field, but is explicitly JSON inlined.
   308  		Inlined struct {
   309  			// User is promoted into the JSON object for Container.
   310  			User string
   311  			// Time cancels out with Base.Time.
   312  			Time string
   313  		} `json:",inline"`
   314  		// ID does not conflict with Base.ID since the JSON name is different.
   315  		ID string `json:"uuid"`
   316  		// Other is not JSON inlined since it has an explicit JSON name.
   317  		Other `json:"other"`
   318  	}
   319  
   320  	// Format an empty Container to show what fields are JSON serializable.
   321  	var input Container
   322  	b, err := json.Marshal(&input)
   323  	if err != nil {
   324  		log.Fatal(err)
   325  	}
   326  	(*jsontext.Value)(&b).Indent() // indent for readability
   327  	fmt.Println(string(b))
   328  
   329  	// Output:
   330  	// {
   331  	// 	"ID": "",
   332  	// 	"Type": 0,
   333  	// 	"User": "",
   334  	// 	"uuid": "",
   335  	// 	"other": {
   336  	// 		"Cost": 0
   337  	// 	}
   338  	// }
   339  }
   340  
   341  // Due to version skew, the set of JSON object members known at compile-time
   342  // may differ from the set of members encountered at execution-time.
   343  // As such, it may be useful to have finer grain handling of unknown members.
   344  // This package supports preserving, rejecting, or discarding such members.
   345  func Example_unknownMembers() {
   346  	const input = `{
   347  		"Name": "Teal",
   348  		"Value": "#008080",
   349  		"WebSafe": false
   350  	}`
   351  	type Color struct {
   352  		Name  string
   353  		Value string
   354  
   355  		// Unknown is a Go struct field that holds unknown JSON object members.
   356  		// It is marked as having this behavior with the "unknown" tag option.
   357  		//
   358  		// The type may be a jsontext.Value or map[string]T.
   359  		Unknown jsontext.Value `json:",unknown"`
   360  	}
   361  
   362  	// By default, unknown members are stored in a Go field marked as "unknown"
   363  	// or ignored if no such field exists.
   364  	var color Color
   365  	err := json.Unmarshal([]byte(input), &color)
   366  	if err != nil {
   367  		log.Fatal(err)
   368  	}
   369  	fmt.Println("Unknown members:", string(color.Unknown))
   370  
   371  	// Specifying RejectUnknownMembers causes Unmarshal
   372  	// to reject the presence of any unknown members.
   373  	err = json.Unmarshal([]byte(input), new(Color), json.RejectUnknownMembers(true))
   374  	var serr *json.SemanticError
   375  	if errors.As(err, &serr) && serr.Err == json.ErrUnknownName {
   376  		fmt.Println("Unmarshal error:", serr.Err, strconv.Quote(serr.JSONPointer.LastToken()))
   377  	}
   378  
   379  	// By default, Marshal preserves unknown members stored in
   380  	// a Go struct field marked as "unknown".
   381  	b, err := json.Marshal(color)
   382  	if err != nil {
   383  		log.Fatal(err)
   384  	}
   385  	fmt.Println("Output with unknown members:   ", string(b))
   386  
   387  	// Specifying DiscardUnknownMembers causes Marshal
   388  	// to discard any unknown members.
   389  	b, err = json.Marshal(color, json.DiscardUnknownMembers(true))
   390  	if err != nil {
   391  		log.Fatal(err)
   392  	}
   393  	fmt.Println("Output without unknown members:", string(b))
   394  
   395  	// Output:
   396  	// Unknown members: {"WebSafe":false}
   397  	// Unmarshal error: unknown object member name "WebSafe"
   398  	// Output with unknown members:    {"Name":"Teal","Value":"#008080","WebSafe":false}
   399  	// Output without unknown members: {"Name":"Teal","Value":"#008080"}
   400  }
   401  
   402  // The "format" tag option can be used to alter the formatting of certain types.
   403  func Example_formatFlags() {
   404  	value := struct {
   405  		BytesBase64    []byte         `json:",format:base64"`
   406  		BytesHex       [8]byte        `json:",format:hex"`
   407  		BytesArray     []byte         `json:",format:array"`
   408  		FloatNonFinite float64        `json:",format:nonfinite"`
   409  		MapEmitNull    map[string]any `json:",format:emitnull"`
   410  		SliceEmitNull  []any          `json:",format:emitnull"`
   411  		TimeDateOnly   time.Time      `json:",format:'2006-01-02'"`
   412  		TimeUnixSec    time.Time      `json:",format:unix"`
   413  		DurationSecs   time.Duration  `json:",format:sec"`
   414  		DurationNanos  time.Duration  `json:",format:nano"`
   415  	}{
   416  		BytesBase64:    []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
   417  		BytesHex:       [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
   418  		BytesArray:     []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
   419  		FloatNonFinite: math.NaN(),
   420  		MapEmitNull:    nil,
   421  		SliceEmitNull:  nil,
   422  		TimeDateOnly:   time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
   423  		TimeUnixSec:    time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
   424  		DurationSecs:   12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond,
   425  		DurationNanos:  12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond,
   426  	}
   427  
   428  	b, err := json.Marshal(&value)
   429  	if err != nil {
   430  		log.Fatal(err)
   431  	}
   432  	(*jsontext.Value)(&b).Indent() // indent for readability
   433  	fmt.Println(string(b))
   434  
   435  	// Output:
   436  	// {
   437  	// 	"BytesBase64": "ASNFZ4mrze8=",
   438  	// 	"BytesHex": "0123456789abcdef",
   439  	// 	"BytesArray": [
   440  	// 		1,
   441  	// 		35,
   442  	// 		69,
   443  	// 		103,
   444  	// 		137,
   445  	// 		171,
   446  	// 		205,
   447  	// 		239
   448  	// 	],
   449  	// 	"FloatNonFinite": "NaN",
   450  	// 	"MapEmitNull": null,
   451  	// 	"SliceEmitNull": null,
   452  	//	"TimeDateOnly": "2000-01-01",
   453  	//	"TimeUnixSec": 946684800,
   454  	//	"DurationSecs": 45296.007008009,
   455  	//	"DurationNanos": 45296007008009
   456  	// }
   457  }
   458  
   459  // When implementing HTTP endpoints, it is common to be operating with an
   460  // [io.Reader] and an [io.Writer]. The [MarshalWrite] and [UnmarshalRead] functions
   461  // assist in operating on such input/output types.
   462  // [UnmarshalRead] reads the entirety of the [io.Reader] to ensure that [io.EOF]
   463  // is encountered without any unexpected bytes after the top-level JSON value.
   464  func Example_serveHTTP() {
   465  	// Some global state maintained by the server.
   466  	var n int64
   467  
   468  	// The "add" endpoint accepts a POST request with a JSON object
   469  	// containing a number to atomically add to the server's global counter.
   470  	// It returns the updated value of the counter.
   471  	http.HandleFunc("/api/add", func(w http.ResponseWriter, r *http.Request) {
   472  		// Unmarshal the request from the client.
   473  		var val struct{ N int64 }
   474  		if err := json.UnmarshalRead(r.Body, &val); err != nil {
   475  			// Inability to unmarshal the input suggests a client-side problem.
   476  			http.Error(w, err.Error(), http.StatusBadRequest)
   477  			return
   478  		}
   479  
   480  		// Marshal a response from the server.
   481  		val.N = atomic.AddInt64(&n, val.N)
   482  		if err := json.MarshalWrite(w, &val); err != nil {
   483  			// Inability to marshal the output suggests a server-side problem.
   484  			// This error is not always observable by the client since
   485  			// json.MarshalWrite may have already written to the output.
   486  			http.Error(w, err.Error(), http.StatusInternalServerError)
   487  			return
   488  		}
   489  	})
   490  }
   491  
   492  // Some Go types have a custom JSON representation where the implementation
   493  // is delegated to some external package. Consequently, the "json" package
   494  // will not know how to use that external implementation.
   495  // For example, the [google.golang.org/protobuf/encoding/protojson] package
   496  // implements JSON for all [google.golang.org/protobuf/proto.Message] types.
   497  // [WithMarshalers] and [WithUnmarshalers] can be used
   498  // to configure "json" and "protojson" to cooperate together.
   499  func Example_protoJSON() {
   500  	// Let protoMessage be "google.golang.org/protobuf/proto".Message.
   501  	type protoMessage interface{ ProtoReflect() }
   502  	// Let foopbMyMessage be a concrete implementation of proto.Message.
   503  	type foopbMyMessage struct{ protoMessage }
   504  	// Let protojson be an import of "google.golang.org/protobuf/encoding/protojson".
   505  	var protojson struct {
   506  		Marshal   func(protoMessage) ([]byte, error)
   507  		Unmarshal func([]byte, protoMessage) error
   508  	}
   509  
   510  	// This value mixes both non-proto.Message types and proto.Message types.
   511  	// It should use the "json" package to handle non-proto.Message types and
   512  	// should use the "protojson" package to handle proto.Message types.
   513  	var value struct {
   514  		// GoStruct does not implement proto.Message and
   515  		// should use the default behavior of the "json" package.
   516  		GoStruct struct {
   517  			Name string
   518  			Age  int
   519  		}
   520  
   521  		// ProtoMessage implements proto.Message and
   522  		// should be handled using protojson.Marshal.
   523  		ProtoMessage *foopbMyMessage
   524  	}
   525  
   526  	// Marshal using protojson.Marshal for proto.Message types.
   527  	b, err := json.Marshal(&value,
   528  		// Use protojson.Marshal as a type-specific marshaler.
   529  		json.WithMarshalers(json.MarshalFunc(protojson.Marshal)))
   530  	if err != nil {
   531  		log.Fatal(err)
   532  	}
   533  
   534  	// Unmarshal using protojson.Unmarshal for proto.Message types.
   535  	err = json.Unmarshal(b, &value,
   536  		// Use protojson.Unmarshal as a type-specific unmarshaler.
   537  		json.WithUnmarshalers(json.UnmarshalFunc(protojson.Unmarshal)))
   538  	if err != nil {
   539  		log.Fatal(err)
   540  	}
   541  }
   542  
   543  // Many error types are not serializable since they tend to be Go structs
   544  // without any exported fields (e.g., errors constructed with [errors.New]).
   545  // Some applications, may desire to marshal an error as a JSON string
   546  // even if these errors cannot be unmarshaled.
   547  func ExampleWithMarshalers_errors() {
   548  	// Response to serialize with some Go errors encountered.
   549  	response := []struct {
   550  		Result string `json:",omitzero"`
   551  		Error  error  `json:",omitzero"`
   552  	}{
   553  		{Result: "Oranges are a good source of Vitamin C."},
   554  		{Error: &strconv.NumError{Func: "ParseUint", Num: "-1234", Err: strconv.ErrSyntax}},
   555  		{Error: &os.PathError{Op: "ReadFile", Path: "/path/to/secret/file", Err: os.ErrPermission}},
   556  	}
   557  
   558  	b, err := json.Marshal(&response,
   559  		// Intercept every attempt to marshal an error type.
   560  		json.WithMarshalers(json.JoinMarshalers(
   561  			// Suppose we consider strconv.NumError to be a safe to serialize:
   562  			// this type-specific marshal function intercepts this type
   563  			// and encodes the error message as a JSON string.
   564  			json.MarshalToFunc(func(enc *jsontext.Encoder, err *strconv.NumError) error {
   565  				return enc.WriteToken(jsontext.String(err.Error()))
   566  			}),
   567  			// Error messages may contain sensitive information that may not
   568  			// be appropriate to serialize. For all errors not handled above,
   569  			// report some generic error message.
   570  			json.MarshalFunc(func(error) ([]byte, error) {
   571  				return []byte(`"internal server error"`), nil
   572  			}),
   573  		)),
   574  		jsontext.Multiline(true)) // expand for readability
   575  	if err != nil {
   576  		log.Fatal(err)
   577  	}
   578  	fmt.Println(string(b))
   579  
   580  	// Output:
   581  	// [
   582  	// 	{
   583  	// 		"Result": "Oranges are a good source of Vitamin C."
   584  	// 	},
   585  	// 	{
   586  	// 		"Error": "strconv.ParseUint: parsing \"-1234\": invalid syntax"
   587  	// 	},
   588  	// 	{
   589  	// 		"Error": "internal server error"
   590  	// 	}
   591  	// ]
   592  }
   593  
   594  // In some applications, the exact precision of JSON numbers needs to be
   595  // preserved when unmarshaling. This can be accomplished using a type-specific
   596  // unmarshal function that intercepts all any types and pre-populates the
   597  // interface value with a [jsontext.Value], which can represent a JSON number exactly.
   598  func ExampleWithUnmarshalers_rawNumber() {
   599  	// Input with JSON numbers beyond the representation of a float64.
   600  	const input = `[false, 1e-1000, 3.141592653589793238462643383279, 1e+1000, true]`
   601  
   602  	var value any
   603  	err := json.Unmarshal([]byte(input), &value,
   604  		// Intercept every attempt to unmarshal into the any type.
   605  		json.WithUnmarshalers(
   606  			json.UnmarshalFromFunc(func(dec *jsontext.Decoder, val *any) error {
   607  				// If the next value to be decoded is a JSON number,
   608  				// then provide a concrete Go type to unmarshal into.
   609  				if dec.PeekKind() == '0' {
   610  					*val = jsontext.Value(nil)
   611  				}
   612  				// Return SkipFunc to fallback on default unmarshal behavior.
   613  				return json.SkipFunc
   614  			}),
   615  		))
   616  	if err != nil {
   617  		log.Fatal(err)
   618  	}
   619  	fmt.Println(value)
   620  
   621  	// Sanity check.
   622  	want := []any{false, jsontext.Value("1e-1000"), jsontext.Value("3.141592653589793238462643383279"), jsontext.Value("1e+1000"), true}
   623  	if !reflect.DeepEqual(value, want) {
   624  		log.Fatalf("value mismatch:\ngot  %v\nwant %v", value, want)
   625  	}
   626  
   627  	// Output:
   628  	// [false 1e-1000 3.141592653589793238462643383279 1e+1000 true]
   629  }
   630  
   631  // When using JSON for parsing configuration files,
   632  // the parsing logic often needs to report an error with a line and column
   633  // indicating where in the input an error occurred.
   634  func ExampleWithUnmarshalers_recordOffsets() {
   635  	// Hypothetical configuration file.
   636  	const input = `[
   637  		{"Source": "192.168.0.100:1234", "Destination": "192.168.0.1:80"},
   638  		{"Source": "192.168.0.251:4004"},
   639  		{"Source": "192.168.0.165:8080", "Destination": "0.0.0.0:80"}
   640  	]`
   641  	type Tunnel struct {
   642  		Source      netip.AddrPort
   643  		Destination netip.AddrPort
   644  
   645  		// ByteOffset is populated during unmarshal with the byte offset
   646  		// within the JSON input of the JSON object for this Go struct.
   647  		ByteOffset int64 `json:"-"` // metadata to be ignored for JSON serialization
   648  	}
   649  
   650  	var tunnels []Tunnel
   651  	err := json.Unmarshal([]byte(input), &tunnels,
   652  		// Intercept every attempt to unmarshal into the Tunnel type.
   653  		json.WithUnmarshalers(
   654  			json.UnmarshalFromFunc(func(dec *jsontext.Decoder, tunnel *Tunnel) error {
   655  				// Decoder.InputOffset reports the offset after the last token,
   656  				// but we want to record the offset before the next token.
   657  				//
   658  				// Call Decoder.PeekKind to buffer enough to reach the next token.
   659  				// Add the number of leading whitespace, commas, and colons
   660  				// to locate the start of the next token.
   661  				dec.PeekKind()
   662  				unread := dec.UnreadBuffer()
   663  				n := len(unread) - len(bytes.TrimLeft(unread, " \n\r\t,:"))
   664  				tunnel.ByteOffset = dec.InputOffset() + int64(n)
   665  
   666  				// Return SkipFunc to fallback on default unmarshal behavior.
   667  				return json.SkipFunc
   668  			}),
   669  		))
   670  	if err != nil {
   671  		log.Fatal(err)
   672  	}
   673  
   674  	// lineColumn converts a byte offset into a one-indexed line and column.
   675  	// The offset must be within the bounds of the input.
   676  	lineColumn := func(input string, offset int) (line, column int) {
   677  		line = 1 + strings.Count(input[:offset], "\n")
   678  		column = 1 + offset - (strings.LastIndex(input[:offset], "\n") + len("\n"))
   679  		return line, column
   680  	}
   681  
   682  	// Verify that the configuration file is valid.
   683  	for _, tunnel := range tunnels {
   684  		if !tunnel.Source.IsValid() || !tunnel.Destination.IsValid() {
   685  			line, column := lineColumn(input, int(tunnel.ByteOffset))
   686  			fmt.Printf("%d:%d: source and destination must both be specified", line, column)
   687  		}
   688  	}
   689  
   690  	// Output:
   691  	// 3:3: source and destination must both be specified
   692  }
   693  

View as plain text