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