tomnomnom/gron · error
failed to merge statements
Error message
failed to merge statements
What it means
This error is returned by gron's toInterface (statements.go:302) when the intermediate parsed statements derived from gron-format input cannot be recursively merged into a single JSON-like value. During ungron, multiple assignments (e.g. `json.a.b = 1` and `json.a.c = [2]`) are folded together pairwise via recursiveMerge; the merge fails when two statements assign incompatible container types to the same key (object vs array), or when an unexpected type shows up. The library wraps the underlying merge error (e.g. "cannot merge object with non-object") with this message.
Source
Thrown at statements.go:302
// no problem :)
case errRecoverable:
continue
default:
return nil, errors.Wrapf(err, "ungron failed for `%s`", s)
}
parsed = append(parsed, u)
}
if len(parsed) == 0 {
return nil, fmt.Errorf("no statements were parsed")
}
merged := parsed[0]
for _, p := range parsed[1:] {
m, err := recursiveMerge(merged, p)
if err != nil {
return nil, errors.Wrap(err, "failed to merge statements")
}
merged = m
}
return merged, nil
}
// Less compares two statements for sort.Sort
// Implements a natural sort to keep array indexes in order
func (ss statements) Less(a, b int) bool {
// ss[a] and ss[b] are both slices of tokens. The first
// thing we need to do is find the first token (if any)
// that differs, then we can use that token to decide
// if ss[a] or ss[b] should come first in the sort.
diffIndex := -1
for i := range ss[a] {
View on GitHub (pinned to 88a6234ea2)
Solutions
- Inspect the gron-format input for a key that is assigned both an object (json.k = {}) and an array (json.k = []) in different lines; fix the input so each key has one consistent container type.
- If concatenating gron output from multiple sources, verify they share the same structure at overlapping paths, or drop/rename the conflicting keys.
- If generating statements programmatically, ensure assignments at the same path always use the same container kind (both map or both slice).
- If the input is meant to be irreconcilable, handle the error and surface it to the user rather than retrying - ungron cannot guess which shape is correct.
Example fix
// before: conflicting statements passed to ungron
// json.a.b = 1
// json.a[0] = 2
// after: make the container type consistent
// json.a.b = 1
// json.a.c = 2
// or guard the call:
// before
v, err := ungron(u.ToInterface())
// after
v, err := ungron(u.ToInterface())
if err != nil && strings.Contains(err.Error(), "failed to merge statements") {
return fmt.Errorf("input lines assign different types to the same key: %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-scan gron lines so each path resolves to one container kind
func conflictingContainers(lines []string) error {
kinds := map[string]string{}
for _, l := range lines {
path, val, ok := splitGronLine(l) // e.g. "json.a" / "{...}"
if !ok { continue }
kind := "scalar"
switch {
case strings.HasPrefix(val, "{"):
kind = "object"
case strings.HasPrefix(val, "["):
kind = "array"
}
if prev, seen := kinds[path]; seen && prev != kind &&
(prev == "object" || prev == "array") &&
(kind == "object" || kind == "array") {
return fmt.Errorf("path %s assigned both %s and %s", path, prev, kind)
}
kinds[path] = kind
}
return nil
} Type guard
// Go: narrow the wrapped error before acting
func isMergeFailure(err error) bool {
if err == nil { return false }
msg := err.Error()
return strings.Contains(msg, "failed to merge statements") ||
strings.Contains(msg, "cannot merge object with non-object") ||
strings.Contains(msg, "cannot merge array with non-array")
} Try / catch
// Go has no try/catch; use errors.Is/As plus message inspection since gron wraps with github.com/pkg/errors
v, err := stmts.ToInterface()
if err != nil {
if isMergeFailure(err) {
return nil, fmt.Errorf("gron input assigns conflicting types to the same key: %w", err)
}
return nil, err
} Prevention
- Only feed ungron lines that came from gron itself or from a single consistent JSON document.
- When concatenating gron output from multiple files, diff overlapping key paths first.
- Check that every path prefix uses one bracket style consistently: dots for object keys, [n] for array indices.
- Validate input with a pre-scan before calling Ungron, and surface a targeted error message instead of the raw wrap.
- Add a regression test for any input shape that triggers this so future gron/ungron version changes are caught.
When it happens
Trigger: Calling gron.Ungron (or ungron.Statements.ToInterface) with a set of statements where the same key path is assigned a map in one statement and an array in another, e.g. feeding ungron output like `json.a = {}` and `json.a = [1]`, or input where a value is both an object and a scalar/array across statements. Any recursiveMerge returning an error while folding parsed[1:] into parsed[0] triggers it.
Common situations: Hand-editing or partially pasting gron output so one line treats a key as `json.foo.x = 1` and another as `json.foo[0] = ...`; concatenating gron output from two different JSON files with divergent structures; programmatic generation of statements with a type mismatch at a shared key; corrupted or truncated gron output where array indices became map keys or vice versa.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- non-assignment statement
- invalid JSON layout
- invalid statement
- statement has no value
- failed to form statements: %s
AI-assisted analysis of tomnomnom/gron@88a6234ea2 (2026-09-06).
Data as JSON: /api/errors/036bca0ac5223509.
Report an issue: GitHub.