Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ jobs:
go-version: "1.24"
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.8.0
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ go-toml is a TOML library for Go. The goal is to provide an easy-to-use and effi

- Follow existing code format and structure
- Code must pass `go fmt`
- Code must pass linting with the same golangci-lint version as CI (see version in `.github/workflows/lint.yml`):
```bash
# Install specific version (check lint.yml for current version)
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin <version>
# Run linter
golangci-lint run ./...
```

### Commit Messages

Expand Down
2 changes: 1 addition & 1 deletion errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ func positionAtEnd(b []byte) (row int, column int) {
}
}

return
return row, column
}

// subsliceOffset returns the byte offset of subslice within data.
Expand Down
2 changes: 1 addition & 1 deletion internal/imported_tests/unmarshal_imported_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1996,7 +1996,7 @@ func TestDecoderStrict(t *testing.T) {
var se *toml.StrictMissingError
assert.True(t, errors.As(err, &se))

keys := []toml.Key{}
keys := make([]toml.Key, 0, len(se.Errors))

for _, e := range se.Errors {
keys = append(keys, e.Key())
Expand Down
9 changes: 8 additions & 1 deletion marshaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,14 @@ func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte
v := iter.Value()

if isNil(v) {
continue
// For nil pointers, convert to zero value of the element type.
// This allows round-trip marshaling of maps with nil pointer values.
// For nil interfaces and nil maps, skip since we can't derive a type.
if v.Kind() == reflect.Ptr {
v = reflect.Zero(v.Type().Elem())
} else {
continue
}
}

k, err := enc.keyToString(iter.Key())
Expand Down
48 changes: 47 additions & 1 deletion marshaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,12 +619,36 @@ hello = 'world'
expected: ``,
},
{
desc: "nil value in map is ignored",
desc: "nil interface value in map is ignored",
v: map[string]interface{}{
"A": nil,
},
expected: ``,
},
{
desc: "nil pointer to struct in map produces empty table",
v: map[string]*struct{}{
"A": nil,
},
expected: `[A]
`,
},
{
desc: "nil pointer to int in map produces zero value",
v: map[string]*int{
"A": nil,
},
expected: `A = 0
`,
},
{
desc: "nil pointer to string in map produces empty string",
v: map[string]*string{
"A": nil,
},
expected: `A = ''
`,
},
{
desc: "new line in table key",
v: map[string]interface{}{
Expand Down Expand Up @@ -2193,3 +2217,25 @@ port = 4242
`
assert.Equal(t, expected, string(out))
}

// TestMarshalIssue975 tests that nil pointer values in maps are marshaled as
// empty tables, allowing round-trip marshaling to work correctly.
// See https://github.com/pelletier/go-toml/issues/975
func TestMarshalIssue975(t *testing.T) {
// Test case from the issue: map[string]*struct{}
oldMap := map[string]*struct{}{
"foo": nil,
}

doc, err := toml.Marshal(&oldMap)
assert.NoError(t, err)
assert.Equal(t, "[foo]\n", string(doc))

var newMap map[string]*struct{}
err = toml.Unmarshal(doc, &newMap)
assert.NoError(t, err)

// Verify the key is preserved after round-trip
_, exists := newMap["foo"]
assert.True(t, exists, "key 'foo' should exist after round-trip")
}