fix: encode mixed arrays with inline table elements

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-17 19:49:50 +02:00
parent 2737a5ac87
commit 3ac0b1e301
5 changed files with 157 additions and 3 deletions
+62 -2
View File
@@ -7,6 +7,7 @@ import (
"bytes"
"context"
"fmt"
"maps"
"math"
"reflect"
"slices"
@@ -313,7 +314,17 @@ func addArrayValue(doc *tomlDoc, name string, v reflect.Value, ctx string) error
return doc.appendScalar(name, []any{}, ctx)
}
if isTableElementValue(v.Index(0)) {
// An array keeps the [[header]] form only when every element is a table.
// TOML lets one array mix tables with scalars, and that mix renders as a
// value array with the table elements written inline.
allTables := true
for i := range n {
if !isTableElementValue(v.Index(i)) {
allTables = false
break
}
}
if allTables {
subs := make([]*tomlDoc, n)
for i := range n {
if i%ctxCheckInterval == 0 {
@@ -347,7 +358,8 @@ func addArrayValue(doc *tomlDoc, name string, v reflect.Value, ctx string) error
return nil
}
// Regular array of scalars.
// Value array. Table elements normalise to map[string]any and the emitter
// writes them as inline tables.
items := make([]any, n)
for i := range n {
if i%ctxCheckInterval == 0 {
@@ -382,11 +394,22 @@ func addArrayValue(doc *tomlDoc, name string, v reflect.Value, ctx string) error
// nested-array representations the emitter understands. Slices and arrays are
// recursively normalised so that nested arrays (e.g. [][]int) work.
func normaliseValue(v reflect.Value) (any, error) {
// Map and slice elements arrive wrapped in interface{}; look through them.
for v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
if v.Kind() == reflect.Interface {
return nil, fmt.Errorf("cannot encode nil value")
}
if v.CanInterface() {
if m, ok := v.Interface().(Marshaler); ok {
return m.MarshalTOML()
}
}
// The datetime structs are TOML scalars; the emitter renders each of them.
if t := v.Type(); t == timeGoType || isLocalDateType(t) {
return v.Interface(), nil
}
switch v.Kind() {
case reflect.String:
return v.String(), nil
@@ -402,6 +425,21 @@ func normaliseValue(v reflect.Value) (any, error) {
return int64(u), nil
case reflect.Float32, reflect.Float64:
return v.Float(), nil
case reflect.Map:
// A table nested in a value array has no header form, so it renders
// inline; the keys normalise to strings for the emitter.
if v.Type().Key().Kind() != reflect.String {
return nil, fmt.Errorf("map key must be string, got %s", v.Type().Key())
}
out := make(map[string]any, v.Len())
for _, k := range v.MapKeys() {
val, err := normaliseValue(v.MapIndex(k))
if err != nil {
return nil, fmt.Errorf("[%s]: %w", k.String(), err)
}
out[k.String()] = val
}
return out, nil
case reflect.Slice, reflect.Array:
items := make([]any, v.Len())
for i := range v.Len() {
@@ -683,6 +721,8 @@ func (e *encoder) writeValue(val any) error {
}
e.buf.WriteByte(']')
return nil
case map[string]any:
return e.writeInlineTable(v)
case nil:
return fmt.Errorf("interpres: cannot encode nil value")
default:
@@ -690,6 +730,26 @@ func (e *encoder) writeValue(val any) error {
}
}
// writeInlineTable renders m as a TOML inline table with sorted keys, the
// order buildMapDoc uses for header tables. It backs the table elements of a
// value array, where the [[header]] form is not available.
func (e *encoder) writeInlineTable(m map[string]any) error {
keys := slices.Sorted(maps.Keys(m))
e.buf.WriteByte('{')
for i, k := range keys {
if i > 0 {
e.buf.WriteString(", ")
}
e.writeKey(k)
e.buf.WriteString(" = ")
if err := e.writeValue(m[k]); err != nil {
return err
}
}
e.buf.WriteByte('}')
return nil
}
func (e *encoder) writeStringVal(s string) error {
if e.opts.literalMultilineAt > 0 && strings.ContainsRune(s, '\n') && len(s) >= e.opts.literalMultilineAt {
return writeLiteralMultilineString(&e.buf, s)