Implement bytes-based Unmarshaler interface for tables and arrays (#873)

This change brings back support for the unstable.Unmarshaler interface
for tables and array tables, addressing issue #873.

Key changes:
- Changed UnmarshalTOML signature from (*Node) to ([]byte) to provide
  raw TOML bytes instead of AST nodes
- Added RawMessage type (similar to json.RawMessage) for capturing raw
  TOML bytes for later processing
- Updated handleKeyValuesUnmarshaler to reconstruct key-value lines
  from the parsed keys and raw value bytes
- Added support for slice types implementing Unmarshaler (e.g., RawMessage)
- Removed unused AST helper functions from unstable/ast.go

The bytes-based interface allows users to:
- Get raw TOML bytes for custom parsing
- Delay TOML decoding using RawMessage
- Implement custom unmarshaling logic for complex types

Tests added for:
- Table unmarshaler with various scenarios
- Array table unmarshaler
- Split tables (same parent defined in multiple places)
- RawMessage usage
- Nested tables and mixed regular fields
This commit is contained in:
Claude
2026-01-15 12:13:14 +00:00
parent 5b6828661c
commit 2762e24a9c
4 changed files with 202 additions and 189 deletions
-46
View File
@@ -143,49 +143,3 @@ func (n *Node) Value() *Node {
func (n *Node) Children() Iterator {
return Iterator{nodes: n.nodes, idx: n.child}
}
// SetNodeSlice sets the backing nodes slice for a node.
// This is used when building synthetic nodes.
func SetNodeSlice(n *Node, nodes *[]Node) {
n.nodes = nodes
}
// SetNodeChild sets the child index for a node.
// This is used when building synthetic nodes.
func SetNodeChild(n *Node, child int32) {
n.child = child
}
// SetNodeNext sets the next sibling index for a node.
// This is used when building synthetic nodes.
func SetNodeNext(n *Node, next int32) {
n.next = next
}
// GetNodeChild returns the child index for a node.
// This is used when copying nodes.
func GetNodeChild(n *Node) int32 {
return n.child
}
// GetNodeNext returns the next sibling index for a node.
// This is used when copying nodes.
func GetNodeNext(n *Node) int32 {
return n.next
}
// GetNodeIndex returns the index of node n in the backing slice,
// using relativeTo's nodes slice as reference.
// Returns -1 if n is not in the slice.
func GetNodeIndex(n *Node, relativeTo *Node) int32 {
if relativeTo.nodes == nil || n == nil {
return -1
}
nodes := *relativeTo.nodes
for i := range nodes {
if &nodes[i] == n {
return int32(i)
}
}
return -1
}
+28 -3
View File
@@ -1,7 +1,32 @@
package unstable
// The Unmarshaler interface may be implemented by types to customize their
// behavior when being unmarshaled from a TOML document.
// Unmarshaler is implemented by types that can unmarshal a TOML
// description of themselves. The input is a valid TOML document
// containing the relevant portion of the parsed document.
//
// For tables (including split tables defined in multiple places),
// the data contains the raw key-value bytes from the original document
// with adjusted table headers to be relative to the unmarshaling target.
type Unmarshaler interface {
UnmarshalTOML(value *Node) error
UnmarshalTOML(data []byte) error
}
// RawMessage is a raw encoded TOML value. It implements Unmarshaler
// and can be used to delay TOML decoding or capture raw content.
//
// Example usage:
//
// type Config struct {
// Plugin RawMessage `toml:"plugin"`
// }
//
// var cfg Config
// toml.NewDecoder(r).EnableUnmarshalerInterface().Decode(&cfg)
// // cfg.Plugin now contains the raw TOML bytes for [plugin]
type RawMessage []byte
// UnmarshalTOML implements Unmarshaler.
func (m *RawMessage) UnmarshalTOML(data []byte) error {
*m = append((*m)[0:0], data...)
return nil
}