k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/internal/third_party/go-json-experiment/json/example_test.go (about) 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 package json_test 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "io" 12 "log" 13 "math" 14 "net/http" 15 "net/netip" 16 "os" 17 "reflect" 18 "strconv" 19 "strings" 20 "sync/atomic" 21 "time" 22 23 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json" 24 ) 25 26 // If a type implements encoding.TextMarshaler and/or encoding.TextUnmarshaler, 27 // then the MarshalText and UnmarshalText methods are used to encode/decode 28 // the value to/from a JSON string. 29 func Example_textMarshal() { 30 // Round-trip marshal and unmarshal a hostname map where the netip.Addr type 31 // implements both encoding.TextMarshaler and encoding.TextUnmarshaler. 32 want := map[netip.Addr]string{ 33 netip.MustParseAddr("192.168.0.100"): "carbonite", 34 netip.MustParseAddr("192.168.0.101"): "obsidian", 35 netip.MustParseAddr("192.168.0.102"): "diamond", 36 } 37 b, err := json.Marshal(&want) 38 if err != nil { 39 log.Fatal(err) 40 } 41 var got map[netip.Addr]string 42 err = json.Unmarshal(b, &got) 43 if err != nil { 44 log.Fatal(err) 45 } 46 47 // Sanity check. 48 if !reflect.DeepEqual(got, want) { 49 log.Fatalf("roundtrip mismatch: got %v, want %v", got, want) 50 } 51 52 // Print the serialized JSON object. Canonicalize the JSON first since 53 // Go map entries are not serialized in a deterministic order. 54 (*json.RawValue)(&b).Canonicalize() 55 (*json.RawValue)(&b).Indent("", "\t") // 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:",nocase"` 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 (*json.RawValue)(&b).Indent("", "\t") // 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 "nocase" option. This permits unmarshaling from inputs that 114 // 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 "nocase", Unmarshal looks for an exact match. 130 var withcase []struct { 131 X bool `json:"firstName"` 132 } 133 if err := json.Unmarshal([]byte(input), &withcase); err != nil { 134 log.Fatal(err) 135 } 136 fmt.Println(withcase) // exactly 1 match found 137 138 // With "nocase", Unmarshal looks first for an exact match, 139 // then for a case-insensitive match if none found. 140 var nocase []struct { 141 X bool `json:"firstName,nocase"` 142 } 143 if err := json.Unmarshal([]byte(input), &nocase); err != nil { 144 log.Fatal(err) 145 } 146 fmt.Println(nocase) // 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 (*json.RawValue)(&b).Indent("", "\t") // 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 ommited 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 (*json.RawValue)(&b).Indent("", "\t") // 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 (*json.RawValue)(&b).Indent("", "\t") // 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 RawValue or map[string]T. 359 Unknown json.RawValue `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 UnmarshalOptions.RejectUnknownMembers causes 372 // Unmarshal to reject the presence of any unknown members. 373 err = json.UnmarshalOptions{ 374 RejectUnknownMembers: true, 375 }.Unmarshal(json.DecodeOptions{}, []byte(input), new(Color)) 376 if err != nil { 377 fmt.Println("Unmarshal error:", errors.Unwrap(err)) 378 } 379 380 // By default, Marshal preserves unknown members stored in 381 // a Go struct field marked as "unknown". 382 b, err := json.Marshal(color) 383 if err != nil { 384 log.Fatal(err) 385 } 386 fmt.Println("Output with unknown members: ", string(b)) 387 388 // Specifying MarshalOptions.DiscardUnknownMembers causes 389 // Marshal to discard any unknown members. 390 b, err = json.MarshalOptions{ 391 DiscardUnknownMembers: true, 392 }.Marshal(json.EncodeOptions{}, color) 393 if err != nil { 394 log.Fatal(err) 395 } 396 fmt.Println("Output without unknown members:", string(b)) 397 398 // Output: 399 // Unknown members: {"WebSafe":false} 400 // Unmarshal error: unknown name "WebSafe" 401 // Output with unknown members: {"Name":"Teal","Value":"#008080","WebSafe":false} 402 // Output without unknown members: {"Name":"Teal","Value":"#008080"} 403 } 404 405 // The "format" tag option can be used to alter the formatting of certain types. 406 func Example_formatFlags() { 407 value := struct { 408 BytesBase64 []byte `json:",format:base64"` 409 BytesHex [8]byte `json:",format:hex"` 410 BytesArray []byte `json:",format:array"` 411 FloatNonFinite float64 `json:",format:nonfinite"` 412 MapEmitNull map[string]any `json:",format:emitnull"` 413 SliceEmitNull []any `json:",format:emitnull"` 414 TimeDateOnly time.Time `json:",format:'2006-01-02'"` 415 DurationNanos time.Duration `json:",format:nanos"` 416 }{ 417 BytesBase64: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, 418 BytesHex: [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, 419 BytesArray: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, 420 FloatNonFinite: math.NaN(), 421 MapEmitNull: nil, 422 SliceEmitNull: nil, 423 TimeDateOnly: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), 424 DurationNanos: time.Second + time.Millisecond + time.Microsecond + time.Nanosecond, 425 } 426 427 b, err := json.Marshal(&value) 428 if err != nil { 429 log.Fatal(err) 430 } 431 (*json.RawValue)(&b).Indent("", "\t") // indent for readability 432 fmt.Println(string(b)) 433 434 // Output: 435 // { 436 // "BytesBase64": "ASNFZ4mrze8=", 437 // "BytesHex": "0123456789abcdef", 438 // "BytesArray": [ 439 // 1, 440 // 35, 441 // 69, 442 // 103, 443 // 137, 444 // 171, 445 // 205, 446 // 239 447 // ], 448 // "FloatNonFinite": "NaN", 449 // "MapEmitNull": null, 450 // "SliceEmitNull": null, 451 // "TimeDateOnly": "2000-01-01", 452 // "DurationNanos": 1001001001 453 // } 454 } 455 456 // When implementing HTTP endpoints, it is common to be operating with an 457 // io.Reader and an io.Writer. The UnmarshalFull and MarshalFull functions 458 // assist in operating on such input/output types. 459 // UnmarshalFull reads the entirety of the io.Reader to ensure that io.EOF 460 // is encountered without any unexpected bytes after the top-level JSON value. 461 func Example_serveHTTP() { 462 // Some global state maintained by the server. 463 var n int64 464 465 // The "add" endpoint accepts a POST request with a JSON object 466 // containing a number to atomically add to the server's global counter. 467 // It returns the updated value of the counter. 468 http.HandleFunc("/api/add", func(w http.ResponseWriter, r *http.Request) { 469 // Unmarshal the request from the client. 470 var val struct{ N int64 } 471 if err := json.UnmarshalFull(r.Body, &val); err != nil { 472 // Inability to unmarshal the input suggests a client-side problem. 473 http.Error(w, err.Error(), http.StatusBadRequest) 474 return 475 } 476 477 // Marshal a response from the server. 478 val.N = atomic.AddInt64(&n, val.N) 479 if err := json.MarshalFull(w, &val); err != nil { 480 // Inability to marshal the output suggests a server-side problem. 481 // This error is not always observable by the client since 482 // json.MarshalFull may have already written to the output. 483 http.Error(w, err.Error(), http.StatusInternalServerError) 484 return 485 } 486 }) 487 } 488 489 // Some Go types have a custom JSON represention where the implementation 490 // is delegated to some external package. Consequentely, the "json" package 491 // will not know how to use that external implementation. 492 // For example, the "google.golang.org/protobuf/encoding/protojson" package 493 // implements JSON for all "google.golang.org/protobuf/proto".Message types. 494 // MarshalOptions.Marshalers and UnmarshalOptions.Unmarshalers can be used 495 // to configure "json" and "protojson" to cooperate together. 496 func Example_protoJSON() { 497 // Let protoMessage be "google.golang.org/protobuf/proto".Message. 498 type protoMessage interface{ ProtoReflect() } 499 // Let foopbMyMessage be a concrete implementation of proto.Message. 500 type foopbMyMessage struct{ protoMessage } 501 // Let protojson be an import of "google.golang.org/protobuf/encoding/protojson". 502 var protojson struct { 503 Marshal func(protoMessage) ([]byte, error) 504 Unmarshal func([]byte, protoMessage) error 505 } 506 507 // This value mixes both non-proto.Message types and proto.Message types. 508 // It should use the "json" package to handle non-proto.Message types and 509 // should use the "protojson" package to handle proto.Message types. 510 var value struct { 511 // GoStruct does not implement proto.Message and 512 // should use the default behavior of the "json" package. 513 GoStruct struct { 514 Name string 515 Age int 516 } 517 518 // ProtoMessage implements proto.Message and 519 // should be handled using protojson.Marshal. 520 ProtoMessage *foopbMyMessage 521 } 522 523 // Marshal using protojson.Marshal for proto.Message types. 524 b, err := json.MarshalOptions{ 525 // Use protojson.Marshal as a type-specific marshaler. 526 Marshalers: json.MarshalFuncV1(protojson.Marshal), 527 }.Marshal(json.EncodeOptions{}, &value) 528 if err != nil { 529 log.Fatal(err) 530 } 531 532 // Unmarshal using protojson.Unmarshal for proto.Message types. 533 err = json.UnmarshalOptions{ 534 // Use protojson.Unmarshal as a type-specific unmarshaler. 535 Unmarshalers: json.UnmarshalFuncV1(protojson.Unmarshal), 536 }.Unmarshal(json.DecodeOptions{}, b, &value) 537 if err != nil { 538 log.Fatal(err) 539 } 540 } 541 542 // This example demonstrates the use of the Encoder and Decoder to 543 // parse and modify JSON without unmarshaling it into a concrete Go type. 544 func Example_stringReplace() { 545 // Example input with non-idiomatic use of "Golang" instead of "Go". 546 const input = `{ 547 "title": "Golang version 1 is released", 548 "author": "Andrew Gerrand", 549 "date": "2012-03-28", 550 "text": "Today marks a major milestone in the development of the Golang programming language.", 551 "otherArticles": [ 552 "Twelve Years of Golang", 553 "The Laws of Reflection", 554 "Learn Golang from your browser" 555 ] 556 }` 557 558 // Using a Decoder and Encoder, we can parse through every token, 559 // check and modify the token if necessary, and 560 // write the token to the output. 561 var replacements []string 562 in := strings.NewReader(input) 563 dec := json.NewDecoder(in) 564 out := new(bytes.Buffer) 565 enc := json.EncodeOptions{Indent: "\t"}.NewEncoder(out) // indent for readability 566 for { 567 // Read a token from the input. 568 tok, err := dec.ReadToken() 569 if err != nil { 570 if err == io.EOF { 571 break 572 } 573 log.Fatal(err) 574 } 575 576 // Check whether the token contains the string "Golang" and 577 // replace each occurence with "Go" instead. 578 if tok.Kind() == '"' && strings.Contains(tok.String(), "Golang") { 579 replacements = append(replacements, dec.StackPointer()) 580 tok = json.String(strings.ReplaceAll(tok.String(), "Golang", "Go")) 581 } 582 583 // Write the (possibly modified) token to the output. 584 if err := enc.WriteToken(tok); err != nil { 585 log.Fatal(err) 586 } 587 } 588 589 // Print the list of replacements and the adjusted JSON output. 590 if len(replacements) > 0 { 591 fmt.Println(`Replaced "Golang" with "Go" in:`) 592 for _, where := range replacements { 593 fmt.Println("\t" + where) 594 } 595 fmt.Println() 596 } 597 fmt.Println("Result:", out.String()) 598 599 // Output: 600 // Replaced "Golang" with "Go" in: 601 // /title 602 // /text 603 // /otherArticles/0 604 // /otherArticles/2 605 // 606 // Result: { 607 // "title": "Go version 1 is released", 608 // "author": "Andrew Gerrand", 609 // "date": "2012-03-28", 610 // "text": "Today marks a major milestone in the development of the Go programming language.", 611 // "otherArticles": [ 612 // "Twelve Years of Go", 613 // "The Laws of Reflection", 614 // "Learn Go from your browser" 615 // ] 616 // } 617 } 618 619 // Directly embedding JSON within HTML requires special handling for safety. 620 // Escape certain runes to prevent JSON directly treated as HTML 621 // from being able to perform <script> injection. 622 // 623 // This example shows how to obtain equivalent behavior provided by the 624 // "encoding/json" package that is no longer directly supported by this package. 625 // Newly written code that intermix JSON and HTML should instead be using the 626 // "github.com/google/safehtml" module for safety purposes. 627 func ExampleEncodeOptions_escapeHTML() { 628 page := struct { 629 Title string 630 Body string 631 }{ 632 Title: "Example Embedded Javascript", 633 Body: `<script> console.log("Hello, world!"); </script>`, 634 } 635 636 b, err := json.MarshalOptions{}.Marshal(json.EncodeOptions{ 637 // Escape certain runes within a JSON string so that 638 // JSON will be safe to directly embed inside HTML. 639 EscapeRune: func(r rune) bool { 640 switch r { 641 case '&', '<', '>', '\u2028', '\u2029': 642 return true 643 default: 644 return false 645 } 646 }, 647 // Indent the output for readability. 648 Indent: "\t", 649 }, &page) 650 if err != nil { 651 log.Fatal(err) 652 } 653 fmt.Println(string(b)) 654 655 // Output: 656 // { 657 // "Title": "Example Embedded Javascript", 658 // "Body": "\u003cscript\u003e console.log(\"Hello, world!\"); \u003c/script\u003e" 659 // } 660 } 661 662 // Many error types are not serializable since they tend to be Go structs 663 // without any exported fields (e.g., errors constructed with errors.New). 664 // Some applications, may desire to marshal an error as a JSON string 665 // even if these errors cannot be unmarshaled. 666 func ExampleMarshalOptions_errors() { 667 // Response to serialize with some Go errors encountered. 668 response := []struct { 669 Result string `json:",omitzero"` 670 Error error `json:",omitzero"` 671 }{ 672 {Result: "Oranges are a good source of Vitamin C."}, 673 {Error: &strconv.NumError{Func: "ParseUint", Num: "-1234", Err: strconv.ErrSyntax}}, 674 {Error: &os.PathError{Op: "ReadFile", Path: "/path/to/secret/file", Err: os.ErrPermission}}, 675 } 676 677 b, err := json.MarshalOptions{ 678 // Intercept every attempt to marshal an error type. 679 Marshalers: json.NewMarshalers( 680 // Suppose we consider strconv.NumError to be a safe to serialize: 681 // this type-specific marshal function intercepts this type 682 // and encodes the error message as a JSON string. 683 json.MarshalFuncV2(func(opts json.MarshalOptions, enc *json.Encoder, err *strconv.NumError) error { 684 return enc.WriteToken(json.String(err.Error())) 685 }), 686 // Error messages may contain sensitive information that may not 687 // be appropriate to serialize. For all errors not handled above, 688 // report some generic error message. 689 json.MarshalFuncV1(func(error) ([]byte, error) { 690 return []byte(`"internal server error"`), nil 691 }), 692 ), 693 }.Marshal(json.EncodeOptions{ 694 Indent: "\t", // indent for readability 695 }, &response) 696 if err != nil { 697 log.Fatal(err) 698 } 699 fmt.Println(string(b)) 700 701 // Output: 702 // [ 703 // { 704 // "Result": "Oranges are a good source of Vitamin C." 705 // }, 706 // { 707 // "Error": "strconv.ParseUint: parsing \"-1234\": invalid syntax" 708 // }, 709 // { 710 // "Error": "internal server error" 711 // } 712 // ] 713 } 714 715 // In some applications, the exact precision of JSON numbers needs to be 716 // preserved when unmarshaling. This can be accomplished using a type-specific 717 // unmarshal function that intercepts all any types and pre-populates the 718 // interface value with a RawValue, which can represent a JSON number exactly. 719 func ExampleUnmarshalOptions_rawNumber() { 720 // Input with JSON numbers beyond the representation of a float64. 721 const input = `[false, 1e-1000, 3.141592653589793238462643383279, 1e+1000, true]` 722 723 var value any 724 err := json.UnmarshalOptions{ 725 // Intercept every attempt to unmarshal into the any type. 726 Unmarshalers: json.UnmarshalFuncV2(func(opts json.UnmarshalOptions, dec *json.Decoder, val *any) error { 727 // If the next value to be decoded is a JSON number, 728 // then provide a concrete Go type to unmarshal into. 729 if dec.PeekKind() == '0' { 730 *val = json.RawValue(nil) 731 } 732 // Return SkipFunc to fallback on default unmarshal behavior. 733 return json.SkipFunc 734 }), 735 }.Unmarshal(json.DecodeOptions{}, []byte(input), &value) 736 if err != nil { 737 log.Fatal(err) 738 } 739 fmt.Println(value) 740 741 // Sanity check. 742 want := []any{false, json.RawValue("1e-1000"), json.RawValue("3.141592653589793238462643383279"), json.RawValue("1e+1000"), true} 743 if !reflect.DeepEqual(value, want) { 744 log.Fatalf("value mismatch:\ngot %v\nwant %v", value, want) 745 } 746 747 // Output: 748 // [false 1e-1000 3.141592653589793238462643383279 1e+1000 true] 749 } 750 751 // When using JSON for parsing configuration files, 752 // the parsing logic often needs to report an error with a line and column 753 // indicating where in the input an error occurred. 754 func ExampleUnmarshalOptions_recordOffsets() { 755 // Hypothetical configuration file. 756 const input = `[ 757 {"Source": "192.168.0.100:1234", "Destination": "192.168.0.1:80"}, 758 {"Source": "192.168.0.251:4004"}, 759 {"Source": "192.168.0.165:8080", "Destination": "0.0.0.0:80"} 760 ]` 761 type Tunnel struct { 762 Source netip.AddrPort 763 Destination netip.AddrPort 764 765 // ByteOffset is populated during unmarshal with the byte offset 766 // within the JSON input of the JSON object for this Go struct. 767 ByteOffset int64 `json:"-"` // metadata to be ignored for JSON serialization 768 } 769 770 var tunnels []Tunnel 771 err := json.UnmarshalOptions{ 772 // Intercept every attempt to unmarshal into the Tunnel type. 773 Unmarshalers: json.UnmarshalFuncV2(func(opts json.UnmarshalOptions, dec *json.Decoder, tunnel *Tunnel) error { 774 // Decoder.InputOffset reports the offset after the last token, 775 // but we want to record the offset before the next token. 776 // 777 // Call Decoder.PeekKind to buffer enough to reach the next token. 778 // Add the number of leading whitespace, commas, and colons 779 // to locate the start of the next token. 780 dec.PeekKind() 781 unread := dec.UnreadBuffer() 782 n := len(unread) - len(bytes.TrimLeft(unread, " \n\r\t,:")) 783 tunnel.ByteOffset = dec.InputOffset() + int64(n) 784 785 // Return SkipFunc to fallback on default unmarshal behavior. 786 return json.SkipFunc 787 }), 788 }.Unmarshal(json.DecodeOptions{}, []byte(input), &tunnels) 789 if err != nil { 790 log.Fatal(err) 791 } 792 793 // lineColumn converts a byte offset into a one-indexed line and column. 794 // The offset must be within the bounds of the input. 795 lineColumn := func(input string, offset int) (line, column int) { 796 line = 1 + strings.Count(input[:offset], "\n") 797 column = 1 + offset - (strings.LastIndex(input[:offset], "\n") + len("\n")) 798 return line, column 799 } 800 801 // Verify that the configuration file is valid. 802 for _, tunnel := range tunnels { 803 if !tunnel.Source.IsValid() || !tunnel.Destination.IsValid() { 804 line, column := lineColumn(input, int(tunnel.ByteOffset)) 805 fmt.Printf("%d:%d: source and destination must both be specified", line, column) 806 } 807 } 808 809 // Output: 810 // 3:3: source and destination must both be specified 811 }