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