tomnomnom/gron · error

non-assignment statement

Error message

non-assignment statement

What it means

statement.jsonify() converts a gron assignment statement into a JSON-equivalent form. It only accepts statements that look like a complete assignment: at least 4 tokens, starting with a bare word, with '=' as the third-to-last token and ';' as the last. If the token slice doesn't match this shape, jsonify returns "non-assignment statement".

Source

Thrown at statements.go:79

	return append(
		new,
		token{".", typDot},
		token{k, typBare},
	)
}

// jsonify converts an assignment statement to a JSON representation
func (s statement) jsonify() (statement, error) {
	// If m is the number of keys occurring in the left hand side
	// of s, then len(s) is in between 2*m+4 and 3*m+4. The resultant
	// statement j (carrying the JSON representation) is always 2*m+5
	// long. So len(s)+1 ≥ 2*m+5 = len(j). Therefore an initaial
	// allocation of j with capacity len(s)+1 will allow us to carry
	// through without reallocation.
	j := make(statement, 0, len(s)+1)
	if len(s) < 4 || s[0].typ != typBare || s[len(s)-3].typ != typEquals ||
		s[len(s)-1].typ != typSemi {
		return nil, errors.New("non-assignment statement")
	}

	j = append(j, token{"[", typLBrace})
	j = append(j, token{"[", typLBrace})
	for _, t := range s[1 : len(s)-3] {
		switch t.typ {
		case typNumericKey, typQuotedKey:
			j = append(j, t)
			j = append(j, token{",", typComma})
		case typBare:
			j = append(j, token{quoteString(t.text), typQuotedKey})
			j = append(j, token{",", typComma})
		}
	}
	if j[len(j)-1].typ == typComma {
		j = j[:len(j)-1]
	}
	j = append(j, token{"]", typLBrace})

View on GitHub (pinned to 88a6234ea2)

Solutions

  1. Ensure the statement ends with '=' followed by a value and a ';' before calling jsonify
  2. Only call jsonify on statements produced by statementsFromJSON / the gron pipeline, not on hand-assembled token fragments
  3. Check statement length and token types (s[0].typ == typBare, s[len-3].typ == typEquals, s[len-1].typ == typSemi) before calling jsonify

Example fix

// before
s := statement{{"json", typBare}}
j, err := s.jsonify() // non-assignment statement

// after
s := statement{{"json", typBare}, {"=", typEquals}, {"null", typNull}, {";", typSemi}}
j, err := s.jsonify() // ok
Defensive patterns

Strategy: validation

Validate before calling

func isAssignment(s statement) bool {
	return len(s) >= 4 && s[0].typ == typBare &&
		s[len(s)-3].typ == typEquals && s[len(s)-1].typ == typSemi
}
if !isAssignment(s) { /* skip or fix before calling jsonify */ }

Type guard

func isAssignment(s statement) bool {
	return len(s) >= 4 && s[0].typ == typBare &&
		s[len(s)-3].typ == typEquals && s[len(s)-1].typ == typSemi
}

Try / catch

j, err := s.jsonify()
if err != nil {
	if err.Error() == "non-assignment statement" {
		// skip fragment / log and continue
	}
	return err
}

Prevention

When it happens

Trigger: Calling jsonify() on a statement built from tokens that are not a full `key = value;` assignment — e.g. a bare-key fragment with no '=' terminator, an empty or truncated statement, or a prefix statement missing the trailing semicolon.

Common situations: Piping `gron --json` output back through gron's JSON-formatted path, or programmatically constructing statement tokens and calling jsonify on partial statements (e.g. key-only fragments produced while building output incrementally).

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of tomnomnom/gron@88a6234ea2 (2026-09-06). Data as JSON: /api/errors/5dd7d29f0e749faf. Report an issue: GitHub.