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 // The "format" tag option can be used to alter the formatting of certain types. 342 func Example_formatFlags() { 343 value := struct { 344 BytesBase64 []byte `json:",format:base64"` 345 BytesHex [8]byte `json:",format:hex"` 346 BytesArray []byte `json:",format:array"` 347 FloatNonFinite float64 `json:",format:nonfinite"` 348 MapEmitNull map[string]any `json:",format:emitnull"` 349 SliceEmitNull []any `json:",format:emitnull"` 350 TimeDateOnly time.Time `json:",format:'2006-01-02'"` 351 TimeUnixSec time.Time `json:",format:unix"` 352 DurationSecs time.Duration `json:",format:sec"` 353 DurationNanos time.Duration `json:",format:nano"` 354 DurationISO8601 time.Duration `json:",format:iso8601"` 355 }{ 356 BytesBase64: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, 357 BytesHex: [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, 358 BytesArray: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, 359 FloatNonFinite: math.NaN(), 360 MapEmitNull: nil, 361 SliceEmitNull: nil, 362 TimeDateOnly: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), 363 TimeUnixSec: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), 364 DurationSecs: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond, 365 DurationNanos: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond, 366 DurationISO8601: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond, 367 } 368 369 b, err := json.Marshal(&value) 370 if err != nil { 371 log.Fatal(err) 372 } 373 (*jsontext.Value)(&b).Indent() // indent for readability 374 fmt.Println(string(b)) 375 376 // Output: 377 // { 378 // "BytesBase64": "ASNFZ4mrze8=", 379 // "BytesHex": "0123456789abcdef", 380 // "BytesArray": [ 381 // 1, 382 // 35, 383 // 69, 384 // 103, 385 // 137, 386 // 171, 387 // 205, 388 // 239 389 // ], 390 // "FloatNonFinite": "NaN", 391 // "MapEmitNull": null, 392 // "SliceEmitNull": null, 393 // "TimeDateOnly": "2000-01-01", 394 // "TimeUnixSec": 946684800, 395 // "DurationSecs": 45296.007008009, 396 // "DurationNanos": 45296007008009, 397 // "DurationISO8601": "PT12H34M56.007008009S" 398 // } 399 } 400 401 // When implementing HTTP endpoints, it is common to be operating with an 402 // [io.Reader] and an [io.Writer]. The [MarshalWrite] and [UnmarshalRead] functions 403 // assist in operating on such input/output types. 404 // [UnmarshalRead] reads the entirety of the [io.Reader] to ensure that [io.EOF] 405 // is encountered without any unexpected bytes after the top-level JSON value. 406 func Example_serveHTTP() { 407 // Some global state maintained by the server. 408 var n int64 409 410 // The "add" endpoint accepts a POST request with a JSON object 411 // containing a number to atomically add to the server's global counter. 412 // It returns the updated value of the counter. 413 http.HandleFunc("/api/add", func(w http.ResponseWriter, r *http.Request) { 414 // Unmarshal the request from the client. 415 var val struct{ N int64 } 416 if err := json.UnmarshalRead(r.Body, &val); err != nil { 417 // Inability to unmarshal the input suggests a client-side problem. 418 http.Error(w, err.Error(), http.StatusBadRequest) 419 return 420 } 421 422 // Marshal a response from the server. 423 val.N = atomic.AddInt64(&n, val.N) 424 if err := json.MarshalWrite(w, &val); err != nil { 425 // Inability to marshal the output suggests a server-side problem. 426 // This error is not always observable by the client since 427 // json.MarshalWrite may have already written to the output. 428 http.Error(w, err.Error(), http.StatusInternalServerError) 429 return 430 } 431 }) 432 } 433 434 // Some Go types have a custom JSON representation where the implementation 435 // is delegated to some external package. Consequently, the "json" package 436 // will not know how to use that external implementation. 437 // For example, the [google.golang.org/protobuf/encoding/protojson] package 438 // implements JSON for all [google.golang.org/protobuf/proto.Message] types. 439 // [WithMarshalers] and [WithUnmarshalers] can be used 440 // to configure "json" and "protojson" to cooperate together. 441 func Example_protoJSON() { 442 // Let protoMessage be "google.golang.org/protobuf/proto".Message. 443 type protoMessage interface{ ProtoReflect() } 444 // Let foopbMyMessage be a concrete implementation of proto.Message. 445 type foopbMyMessage struct{ protoMessage } 446 // Let protojson be an import of "google.golang.org/protobuf/encoding/protojson". 447 var protojson struct { 448 Marshal func(protoMessage) ([]byte, error) 449 Unmarshal func([]byte, protoMessage) error 450 } 451 452 // This value mixes both non-proto.Message types and proto.Message types. 453 // It should use the "json" package to handle non-proto.Message types and 454 // should use the "protojson" package to handle proto.Message types. 455 var value struct { 456 // GoStruct does not implement proto.Message and 457 // should use the default behavior of the "json" package. 458 GoStruct struct { 459 Name string 460 Age int 461 } 462 463 // ProtoMessage implements proto.Message and 464 // should be handled using protojson.Marshal. 465 ProtoMessage *foopbMyMessage 466 } 467 468 // Marshal using protojson.Marshal for proto.Message types. 469 b, err := json.Marshal(&value, 470 // Use protojson.Marshal as a type-specific marshaler. 471 json.WithMarshalers(json.MarshalFunc(protojson.Marshal))) 472 if err != nil { 473 log.Fatal(err) 474 } 475 476 // Unmarshal using protojson.Unmarshal for proto.Message types. 477 err = json.Unmarshal(b, &value, 478 // Use protojson.Unmarshal as a type-specific unmarshaler. 479 json.WithUnmarshalers(json.UnmarshalFunc(protojson.Unmarshal))) 480 if err != nil { 481 log.Fatal(err) 482 } 483 } 484 485 // Many error types are not serializable since they tend to be Go structs 486 // without any exported fields (e.g., errors constructed with [errors.New]). 487 // Some applications, may desire to marshal an error as a JSON string 488 // even if these errors cannot be unmarshaled. 489 func ExampleWithMarshalers_errors() { 490 // Response to serialize with some Go errors encountered. 491 response := []struct { 492 Result string `json:",omitzero"` 493 Error error `json:",omitzero"` 494 }{ 495 {Result: "Oranges are a good source of Vitamin C."}, 496 {Error: &strconv.NumError{Func: "ParseUint", Num: "-1234", Err: strconv.ErrSyntax}}, 497 {Error: &os.PathError{Op: "ReadFile", Path: "/path/to/secret/file", Err: os.ErrPermission}}, 498 } 499 500 b, err := json.Marshal(&response, 501 // Intercept every attempt to marshal an error type. 502 json.WithMarshalers(json.JoinMarshalers( 503 // Suppose we consider strconv.NumError to be a safe to serialize: 504 // this type-specific marshal function intercepts this type 505 // and encodes the error message as a JSON string. 506 json.MarshalToFunc(func(enc *jsontext.Encoder, err *strconv.NumError) error { 507 return enc.WriteToken(jsontext.String(err.Error())) 508 }), 509 // Error messages may contain sensitive information that may not 510 // be appropriate to serialize. For all errors not handled above, 511 // report some generic error message. 512 json.MarshalFunc(func(error) ([]byte, error) { 513 return []byte(`"internal server error"`), nil 514 }), 515 )), 516 jsontext.Multiline(true)) // expand for readability 517 if err != nil { 518 log.Fatal(err) 519 } 520 fmt.Println(string(b)) 521 522 // Output: 523 // [ 524 // { 525 // "Result": "Oranges are a good source of Vitamin C." 526 // }, 527 // { 528 // "Error": "strconv.ParseUint: parsing \"-1234\": invalid syntax" 529 // }, 530 // { 531 // "Error": "internal server error" 532 // } 533 // ] 534 } 535 536 // In some applications, the exact precision of JSON numbers needs to be 537 // preserved when unmarshaling. This can be accomplished using a type-specific 538 // unmarshal function that intercepts all any types and pre-populates the 539 // interface value with a [jsontext.Value], which can represent a JSON number exactly. 540 func ExampleWithUnmarshalers_rawNumber() { 541 // Input with JSON numbers beyond the representation of a float64. 542 const input = `[false, 1e-1000, 3.141592653589793238462643383279, 1e+1000, true]` 543 544 var value any 545 err := json.Unmarshal([]byte(input), &value, 546 // Intercept every attempt to unmarshal into the any type. 547 json.WithUnmarshalers( 548 json.UnmarshalFromFunc(func(dec *jsontext.Decoder, val *any) error { 549 // If the next value to be decoded is a JSON number, 550 // then provide a concrete Go type to unmarshal into. 551 if dec.PeekKind() == '0' { 552 *val = jsontext.Value(nil) 553 } 554 // Return ErrUnsupported to fallback on default unmarshal behavior. 555 return errors.ErrUnsupported 556 }), 557 )) 558 if err != nil { 559 log.Fatal(err) 560 } 561 fmt.Println(value) 562 563 // Sanity check. 564 want := []any{false, jsontext.Value("1e-1000"), jsontext.Value("3.141592653589793238462643383279"), jsontext.Value("1e+1000"), true} 565 if !reflect.DeepEqual(value, want) { 566 log.Fatalf("value mismatch:\ngot %v\nwant %v", value, want) 567 } 568 569 // Output: 570 // [false 1e-1000 3.141592653589793238462643383279 1e+1000 true] 571 } 572 573 // When using JSON for parsing configuration files, 574 // the parsing logic often needs to report an error with a line and column 575 // indicating where in the input an error occurred. 576 func ExampleWithUnmarshalers_recordOffsets() { 577 // Hypothetical configuration file. 578 const input = `[ 579 {"Source": "192.168.0.100:1234", "Destination": "192.168.0.1:80"}, 580 {"Source": "192.168.0.251:4004"}, 581 {"Source": "192.168.0.165:8080", "Destination": "0.0.0.0:80"} 582 ]` 583 type Tunnel struct { 584 Source netip.AddrPort 585 Destination netip.AddrPort 586 587 // ByteOffset is populated during unmarshal with the byte offset 588 // within the JSON input of the JSON object for this Go struct. 589 ByteOffset int64 `json:"-"` // metadata to be ignored for JSON serialization 590 } 591 592 var tunnels []Tunnel 593 err := json.Unmarshal([]byte(input), &tunnels, 594 // Intercept every attempt to unmarshal into the Tunnel type. 595 json.WithUnmarshalers( 596 json.UnmarshalFromFunc(func(dec *jsontext.Decoder, tunnel *Tunnel) error { 597 // Decoder.InputOffset reports the offset after the last token, 598 // but we want to record the offset before the next token. 599 // 600 // Call Decoder.PeekKind to buffer enough to reach the next token. 601 // Add the number of leading whitespace, commas, and colons 602 // to locate the start of the next token. 603 dec.PeekKind() 604 unread := dec.UnreadBuffer() 605 n := len(unread) - len(bytes.TrimLeft(unread, " \n\r\t,:")) 606 tunnel.ByteOffset = dec.InputOffset() + int64(n) 607 608 // Return ErrUnsupported to fallback on default unmarshal behavior. 609 return errors.ErrUnsupported 610 }), 611 )) 612 if err != nil { 613 log.Fatal(err) 614 } 615 616 // lineColumn converts a byte offset into a one-indexed line and column. 617 // The offset must be within the bounds of the input. 618 lineColumn := func(input string, offset int) (line, column int) { 619 line = 1 + strings.Count(input[:offset], "\n") 620 column = 1 + offset - (strings.LastIndex(input[:offset], "\n") + len("\n")) 621 return line, column 622 } 623 624 // Verify that the configuration file is valid. 625 for _, tunnel := range tunnels { 626 if !tunnel.Source.IsValid() || !tunnel.Destination.IsValid() { 627 line, column := lineColumn(input, int(tunnel.ByteOffset)) 628 fmt.Printf("%d:%d: source and destination must both be specified", line, column) 629 } 630 } 631 632 // Output: 633 // 3:3: source and destination must both be specified 634 } 635