README.md (17026B)
1 # go-toml v2 2 3 Go library for the [TOML](https://toml.io/en/) format. 4 5 This library supports [TOML v1.0.0](https://toml.io/en/v1.0.0). 6 7 [🐞 Bug Reports](https://github.com/pelletier/go-toml/issues) 8 9 [💬 Anything else](https://github.com/pelletier/go-toml/discussions) 10 11 ## Documentation 12 13 Full API, examples, and implementation notes are available in the Go 14 documentation. 15 16 [![Go Reference](https://pkg.go.dev/badge/github.com/pelletier/go-toml/v2.svg)](https://pkg.go.dev/github.com/pelletier/go-toml/v2) 17 18 ## Import 19 20 ```go 21 import "github.com/pelletier/go-toml/v2" 22 ``` 23 24 See [Modules](#Modules). 25 26 ## Features 27 28 ### Stdlib behavior 29 30 As much as possible, this library is designed to behave similarly as the 31 standard library's `encoding/json`. 32 33 ### Performance 34 35 While go-toml favors usability, it is written with performance in mind. Most 36 operations should not be shockingly slow. See [benchmarks](#benchmarks). 37 38 ### Strict mode 39 40 `Decoder` can be set to "strict mode", which makes it error when some parts of 41 the TOML document was not present in the target structure. This is a great way 42 to check for typos. [See example in the documentation][strict]. 43 44 [strict]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#example-Decoder.DisallowUnknownFields 45 46 ### Contextualized errors 47 48 When most decoding errors occur, go-toml returns [`DecodeError`][decode-err]), 49 which contains a human readable contextualized version of the error. For 50 example: 51 52 ``` 53 2| key1 = "value1" 54 3| key2 = "missing2" 55 | ~~~~ missing field 56 4| key3 = "missing3" 57 5| key4 = "value4" 58 ``` 59 60 [decode-err]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#DecodeError 61 62 ### Local date and time support 63 64 TOML supports native [local date/times][ldt]. It allows to represent a given 65 date, time, or date-time without relation to a timezone or offset. To support 66 this use-case, go-toml provides [`LocalDate`][tld], [`LocalTime`][tlt], and 67 [`LocalDateTime`][tldt]. Those types can be transformed to and from `time.Time`, 68 making them convenient yet unambiguous structures for their respective TOML 69 representation. 70 71 [ldt]: https://toml.io/en/v1.0.0#local-date-time 72 [tld]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalDate 73 [tlt]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalTime 74 [tldt]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalDateTime 75 76 ## Getting started 77 78 Given the following struct, let's see how to read it and write it as TOML: 79 80 ```go 81 type MyConfig struct { 82 Version int 83 Name string 84 Tags []string 85 } 86 ``` 87 88 ### Unmarshaling 89 90 [`Unmarshal`][unmarshal] reads a TOML document and fills a Go structure with its 91 content. For example: 92 93 ```go 94 doc := ` 95 version = 2 96 name = "go-toml" 97 tags = ["go", "toml"] 98 ` 99 100 var cfg MyConfig 101 err := toml.Unmarshal([]byte(doc), &cfg) 102 if err != nil { 103 panic(err) 104 } 105 fmt.Println("version:", cfg.Version) 106 fmt.Println("name:", cfg.Name) 107 fmt.Println("tags:", cfg.Tags) 108 109 // Output: 110 // version: 2 111 // name: go-toml 112 // tags: [go toml] 113 ``` 114 115 [unmarshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Unmarshal 116 117 ### Marshaling 118 119 [`Marshal`][marshal] is the opposite of Unmarshal: it represents a Go structure 120 as a TOML document: 121 122 ```go 123 cfg := MyConfig{ 124 Version: 2, 125 Name: "go-toml", 126 Tags: []string{"go", "toml"}, 127 } 128 129 b, err := toml.Marshal(cfg) 130 if err != nil { 131 panic(err) 132 } 133 fmt.Println(string(b)) 134 135 // Output: 136 // Version = 2 137 // Name = 'go-toml' 138 // Tags = ['go', 'toml'] 139 ``` 140 141 [marshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Marshal 142 143 ## Unstable API 144 145 This API does not yet follow the backward compatibility guarantees of this 146 library. They provide early access to features that may have rough edges or an 147 API subject to change. 148 149 ### Parser 150 151 Parser is the unstable API that allows iterative parsing of a TOML document at 152 the AST level. See https://pkg.go.dev/github.com/pelletier/go-toml/v2/unstable. 153 154 ## Benchmarks 155 156 Execution time speedup compared to other Go TOML libraries: 157 158 <table> 159 <thead> 160 <tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr> 161 </thead> 162 <tbody> 163 <tr><td>Marshal/HugoFrontMatter-2</td><td>1.9x</td><td>1.9x</td></tr> 164 <tr><td>Marshal/ReferenceFile/map-2</td><td>1.7x</td><td>1.8x</td></tr> 165 <tr><td>Marshal/ReferenceFile/struct-2</td><td>2.2x</td><td>2.5x</td></tr> 166 <tr><td>Unmarshal/HugoFrontMatter-2</td><td>2.9x</td><td>2.9x</td></tr> 167 <tr><td>Unmarshal/ReferenceFile/map-2</td><td>2.6x</td><td>2.9x</td></tr> 168 <tr><td>Unmarshal/ReferenceFile/struct-2</td><td>4.4x</td><td>5.3x</td></tr> 169 </tbody> 170 </table> 171 <details><summary>See more</summary> 172 <p>The table above has the results of the most common use-cases. The table below 173 contains the results of all benchmarks, including unrealistic ones. It is 174 provided for completeness.</p> 175 176 <table> 177 <thead> 178 <tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr> 179 </thead> 180 <tbody> 181 <tr><td>Marshal/SimpleDocument/map-2</td><td>1.8x</td><td>2.9x</td></tr> 182 <tr><td>Marshal/SimpleDocument/struct-2</td><td>2.7x</td><td>4.2x</td></tr> 183 <tr><td>Unmarshal/SimpleDocument/map-2</td><td>4.5x</td><td>3.1x</td></tr> 184 <tr><td>Unmarshal/SimpleDocument/struct-2</td><td>6.2x</td><td>3.9x</td></tr> 185 <tr><td>UnmarshalDataset/example-2</td><td>3.1x</td><td>3.5x</td></tr> 186 <tr><td>UnmarshalDataset/code-2</td><td>2.3x</td><td>3.1x</td></tr> 187 <tr><td>UnmarshalDataset/twitter-2</td><td>2.5x</td><td>2.6x</td></tr> 188 <tr><td>UnmarshalDataset/citm_catalog-2</td><td>2.1x</td><td>2.2x</td></tr> 189 <tr><td>UnmarshalDataset/canada-2</td><td>1.6x</td><td>1.3x</td></tr> 190 <tr><td>UnmarshalDataset/config-2</td><td>4.3x</td><td>3.2x</td></tr> 191 <tr><td>[Geo mean]</td><td>2.7x</td><td>2.8x</td></tr> 192 </tbody> 193 </table> 194 <p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p> 195 </details> 196 197 ## Modules 198 199 go-toml uses Go's standard modules system. 200 201 Installation instructions: 202 203 - Go ≥ 1.16: Nothing to do. Use the import in your code. The `go` command deals 204 with it automatically. 205 - Go ≥ 1.13: `GO111MODULE=on go get github.com/pelletier/go-toml/v2`. 206 207 In case of trouble: [Go Modules FAQ][mod-faq]. 208 209 [mod-faq]: https://github.com/golang/go/wiki/Modules#why-does-installing-a-tool-via-go-get-fail-with-error-cannot-find-main-module 210 211 ## Tools 212 213 Go-toml provides three handy command line tools: 214 215 * `tomljson`: Reads a TOML file and outputs its JSON representation. 216 217 ``` 218 $ go install github.com/pelletier/go-toml/v2/cmd/tomljson@latest 219 $ tomljson --help 220 ``` 221 222 * `jsontoml`: Reads a JSON file and outputs a TOML representation. 223 224 ``` 225 $ go install github.com/pelletier/go-toml/v2/cmd/jsontoml@latest 226 $ jsontoml --help 227 ``` 228 229 * `tomll`: Lints and reformats a TOML file. 230 231 ``` 232 $ go install github.com/pelletier/go-toml/v2/cmd/tomll@latest 233 $ tomll --help 234 ``` 235 236 ### Docker image 237 238 Those tools are also available as a [Docker image][docker]. For example, to use 239 `tomljson`: 240 241 ``` 242 docker run -i ghcr.io/pelletier/go-toml:v2 tomljson < example.toml 243 ``` 244 245 Multiple versions are availble on [ghcr.io][docker]. 246 247 [docker]: https://github.com/pelletier/go-toml/pkgs/container/go-toml 248 249 ## Migrating from v1 250 251 This section describes the differences between v1 and v2, with some pointers on 252 how to get the original behavior when possible. 253 254 ### Decoding / Unmarshal 255 256 #### Automatic field name guessing 257 258 When unmarshaling to a struct, if a key in the TOML document does not exactly 259 match the name of a struct field or any of the `toml`-tagged field, v1 tries 260 multiple variations of the key ([code][v1-keys]). 261 262 V2 instead does a case-insensitive matching, like `encoding/json`. 263 264 This could impact you if you are relying on casing to differentiate two fields, 265 and one of them is a not using the `toml` struct tag. The recommended solution 266 is to be specific about tag names for those fields using the `toml` struct tag. 267 268 [v1-keys]: https://github.com/pelletier/go-toml/blob/a2e52561804c6cd9392ebf0048ca64fe4af67a43/marshal.go#L775-L781 269 270 #### Ignore preexisting value in interface 271 272 When decoding into a non-nil `interface{}`, go-toml v1 uses the type of the 273 element in the interface to decode the object. For example: 274 275 ```go 276 type inner struct { 277 B interface{} 278 } 279 type doc struct { 280 A interface{} 281 } 282 283 d := doc{ 284 A: inner{ 285 B: "Before", 286 }, 287 } 288 289 data := ` 290 [A] 291 B = "After" 292 ` 293 294 toml.Unmarshal([]byte(data), &d) 295 fmt.Printf("toml v1: %#v\n", d) 296 297 // toml v1: main.doc{A:main.inner{B:"After"}} 298 ``` 299 300 In this case, field `A` is of type `interface{}`, containing a `inner` struct. 301 V1 sees that type and uses it when decoding the object. 302 303 When decoding an object into an `interface{}`, V2 instead disregards whatever 304 value the `interface{}` may contain and replaces it with a 305 `map[string]interface{}`. With the same data structure as above, here is what 306 the result looks like: 307 308 ```go 309 toml.Unmarshal([]byte(data), &d) 310 fmt.Printf("toml v2: %#v\n", d) 311 312 // toml v2: main.doc{A:map[string]interface {}{"B":"After"}} 313 ``` 314 315 This is to match `encoding/json`'s behavior. There is no way to make the v2 316 decoder behave like v1. 317 318 #### Values out of array bounds ignored 319 320 When decoding into an array, v1 returns an error when the number of elements 321 contained in the doc is superior to the capacity of the array. For example: 322 323 ```go 324 type doc struct { 325 A [2]string 326 } 327 d := doc{} 328 err := toml.Unmarshal([]byte(`A = ["one", "two", "many"]`), &d) 329 fmt.Println(err) 330 331 // (1, 1): unmarshal: TOML array length (3) exceeds destination array length (2) 332 ``` 333 334 In the same situation, v2 ignores the last value: 335 336 ```go 337 err := toml.Unmarshal([]byte(`A = ["one", "two", "many"]`), &d) 338 fmt.Println("err:", err, "d:", d) 339 // err: <nil> d: {[one two]} 340 ``` 341 342 This is to match `encoding/json`'s behavior. There is no way to make the v2 343 decoder behave like v1. 344 345 #### Support for `toml.Unmarshaler` has been dropped 346 347 This method was not widely used, poorly defined, and added a lot of complexity. 348 A similar effect can be achieved by implementing the `encoding.TextUnmarshaler` 349 interface and use strings. 350 351 #### Support for `default` struct tag has been dropped 352 353 This feature adds complexity and a poorly defined API for an effect that can be 354 accomplished outside of the library. 355 356 It does not seem like other format parsers in Go support that feature (the 357 project referenced in the original ticket #202 has not been updated since 2017). 358 Given that go-toml v2 should not touch values not in the document, the same 359 effect can be achieved by pre-filling the struct with defaults (libraries like 360 [go-defaults][go-defaults] can help). Also, string representation is not well 361 defined for all types: it creates issues like #278. 362 363 The recommended replacement is pre-filling the struct before unmarshaling. 364 365 [go-defaults]: https://github.com/mcuadros/go-defaults 366 367 #### `toml.Tree` replacement 368 369 This structure was the initial attempt at providing a document model for 370 go-toml. It allows manipulating the structure of any document, encoding and 371 decoding from their TOML representation. While a more robust feature was 372 initially planned in go-toml v2, this has been ultimately [removed from 373 scope][nodoc] of this library, with no plan to add it back at the moment. The 374 closest equivalent at the moment would be to unmarshal into an `interface{}` and 375 use type assertions and/or reflection to manipulate the arbitrary 376 structure. However this would fall short of providing all of the TOML features 377 such as adding comments and be specific about whitespace. 378 379 380 #### `toml.Position` are not retrievable anymore 381 382 The API for retrieving the position (line, column) of a specific TOML element do 383 not exist anymore. This was done to minimize the amount of concepts introduced 384 by the library (query path), and avoid the performance hit related to storing 385 positions in the absence of a document model, for a feature that seemed to have 386 little use. Errors however have gained more detailed position 387 information. Position retrieval seems better fitted for a document model, which 388 has been [removed from the scope][nodoc] of go-toml v2 at the moment. 389 390 ### Encoding / Marshal 391 392 #### Default struct fields order 393 394 V1 emits struct fields order alphabetically by default. V2 struct fields are 395 emitted in order they are defined. For example: 396 397 ```go 398 type S struct { 399 B string 400 A string 401 } 402 403 data := S{ 404 B: "B", 405 A: "A", 406 } 407 408 b, _ := tomlv1.Marshal(data) 409 fmt.Println("v1:\n" + string(b)) 410 411 b, _ = tomlv2.Marshal(data) 412 fmt.Println("v2:\n" + string(b)) 413 414 // Output: 415 // v1: 416 // A = "A" 417 // B = "B" 418 419 // v2: 420 // B = 'B' 421 // A = 'A' 422 ``` 423 424 There is no way to make v2 encoder behave like v1. A workaround could be to 425 manually sort the fields alphabetically in the struct definition, or generate 426 struct types using `reflect.StructOf`. 427 428 #### No indentation by default 429 430 V1 automatically indents content of tables by default. V2 does not. However the 431 same behavior can be obtained using [`Encoder.SetIndentTables`][sit]. For example: 432 433 ```go 434 data := map[string]interface{}{ 435 "table": map[string]string{ 436 "key": "value", 437 }, 438 } 439 440 b, _ := tomlv1.Marshal(data) 441 fmt.Println("v1:\n" + string(b)) 442 443 b, _ = tomlv2.Marshal(data) 444 fmt.Println("v2:\n" + string(b)) 445 446 buf := bytes.Buffer{} 447 enc := tomlv2.NewEncoder(&buf) 448 enc.SetIndentTables(true) 449 enc.Encode(data) 450 fmt.Println("v2 Encoder:\n" + string(buf.Bytes())) 451 452 // Output: 453 // v1: 454 // 455 // [table] 456 // key = "value" 457 // 458 // v2: 459 // [table] 460 // key = 'value' 461 // 462 // 463 // v2 Encoder: 464 // [table] 465 // key = 'value' 466 ``` 467 468 [sit]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Encoder.SetIndentTables 469 470 #### Keys and strings are single quoted 471 472 V1 always uses double quotes (`"`) around strings and keys that cannot be 473 represented bare (unquoted). V2 uses single quotes instead by default (`'`), 474 unless a character cannot be represented, then falls back to double quotes. As a 475 result of this change, `Encoder.QuoteMapKeys` has been removed, as it is not 476 useful anymore. 477 478 There is no way to make v2 encoder behave like v1. 479 480 #### `TextMarshaler` emits as a string, not TOML 481 482 Types that implement [`encoding.TextMarshaler`][tm] can emit arbitrary TOML in 483 v1. The encoder would append the result to the output directly. In v2 the result 484 is wrapped in a string. As a result, this interface cannot be implemented by the 485 root object. 486 487 There is no way to make v2 encoder behave like v1. 488 489 [tm]: https://golang.org/pkg/encoding/#TextMarshaler 490 491 #### `Encoder.CompactComments` has been removed 492 493 Emitting compact comments is now the default behavior of go-toml. This option 494 is not necessary anymore. 495 496 #### Struct tags have been merged 497 498 V1 used to provide multiple struct tags: `comment`, `commented`, `multiline`, 499 `toml`, and `omitempty`. To behave more like the standard library, v2 has merged 500 `toml`, `multiline`, and `omitempty`. For example: 501 502 ```go 503 type doc struct { 504 // v1 505 F string `toml:"field" multiline:"true" omitempty:"true"` 506 // v2 507 F string `toml:"field,multiline,omitempty"` 508 } 509 ``` 510 511 Has a result, the `Encoder.SetTag*` methods have been removed, as there is just 512 one tag now. 513 514 515 #### `commented` tag has been removed 516 517 There is no replacement for the `commented` tag. This feature would be better 518 suited in a proper document model for go-toml v2, which has been [cut from 519 scope][nodoc] at the moment. 520 521 #### `Encoder.ArraysWithOneElementPerLine` has been renamed 522 523 The new name is `Encoder.SetArraysMultiline`. The behavior should be the same. 524 525 #### `Encoder.Indentation` has been renamed 526 527 The new name is `Encoder.SetIndentSymbol`. The behavior should be the same. 528 529 530 #### Embedded structs behave like stdlib 531 532 V1 defaults to merging embedded struct fields into the embedding struct. This 533 behavior was unexpected because it does not follow the standard library. To 534 avoid breaking backward compatibility, the `Encoder.PromoteAnonymous` method was 535 added to make the encoder behave correctly. Given backward compatibility is not 536 a problem anymore, v2 does the right thing by default: it follows the behavior 537 of `encoding/json`. `Encoder.PromoteAnonymous` has been removed. 538 539 [nodoc]: https://github.com/pelletier/go-toml/discussions/506#discussioncomment-1526038 540 541 ### `query` 542 543 go-toml v1 provided the [`go-toml/query`][query] package. It allowed to run 544 JSONPath-style queries on TOML files. This feature is not available in v2. For a 545 replacement, check out [dasel][dasel]. 546 547 This package has been removed because it was essentially not supported anymore 548 (last commit May 2020), increased the complexity of the code base, and more 549 complete solutions exist out there. 550 551 [query]: https://github.com/pelletier/go-toml/tree/f99d6bbca119636aeafcf351ee52b3d202782627/query 552 [dasel]: https://github.com/TomWright/dasel 553 554 ## Versioning 555 556 Go-toml follows [Semantic Versioning](https://semver.org). The supported version 557 of [TOML](https://github.com/toml-lang/toml) is indicated at the beginning of 558 this document. The last two major versions of Go are supported 559 (see [Go Release Policy](https://golang.org/doc/devel/release.html#policy)). 560 561 ## License 562 563 The MIT License (MIT). Read [LICENSE](LICENSE).