d2lang/d2 · error
failed to unmarshal Position from %q: expected three fields
Error message
failed to unmarshal Position from %q: expected three fields
What it means
Position.UnmarshalText splits the input on ':' and requires exactly three fields: line, column, and byte offset. This error means the position string did not have exactly three colon-separated fields. It surfaces through MakeRange when deserializing positions inside a Range.
Source
Thrown at d2ast/d2ast.go:200
return fmt.Sprintf("%d:%d", p.Line+1, p.Column+1)
}
func (p Position) Debug() string {
return fmt.Sprintf("%d:%d:%d", p.Line, p.Column, p.Byte)
}
// See docs on Range.
func (p Position) MarshalText() ([]byte, error) {
return []byte(fmt.Sprintf("%d:%d:%d", p.Line, p.Column, p.Byte)), nil
}
// See docs on Range.
func (p *Position) UnmarshalText(b []byte) (err error) {
defer xdefer.Errorf(&err, "failed to unmarshal Position from %q", b)
fields := bytes.Split(b, []byte{':'})
if len(fields) != 3 {
return errors.New("expected three fields")
}
p.Line, err = strconv.Atoi(string(fields[0]))
if err != nil {
return err
}
p.Column, err = strconv.Atoi(string(fields[1]))
if err != nil {
return err
}
p.Byte, err = strconv.Atoi(string(fields[2]))
return err
}
// From copies src into p. It's used in the d2parser package to set a node's Range.End to
// the parser's current pos on all return paths with defer.
func (p *Position) From(src *Position) {
*p = *srcView on GitHub (pinned to 0d69dca6f5)
Solutions
- Provide all three colon-separated fields "line:col:byte", e.g. "1:0:0".
- If you only have line:col, compute the byte offset and include it as the third field.
- Use Position.MarshalText output as the source of valid position strings.
Example fix
// before
p.UnmarshalText("1:0")
// after
p.UnmarshalText("1:0:0") Defensive patterns
Strategy: validation
Validate before calling
func validPositionText(s string) bool { return len(strings.Split(s, ":")) == 3 }
if !validPositionText(pos) { return fmt.Errorf("position must be line:col:byte, got %q", pos) } Type guard
func isPositionText(s string) bool { return len(strings.Split(s, ":")) == 3 } Try / catch
var p d2ast.Position
if err := p.UnmarshalText(input); err != nil {
return fmt.Errorf("bad position %q (need line:col:byte): %w", input, err)
} Prevention
- Always serialize positions as three colon-separated fields including the byte offset.
- Use Position.MarshalText output instead of formatting manually.
- Reject two-field line:col strings at input boundaries.
When it happens
Trigger: Calling Position.UnmarshalText (directly or via Range/MakeRange) with strings like "1:0" (two fields) or "1" (one field) instead of "1:0:0".
Common situations: Constructing positions from line:column pairs (only two fields) without the byte-offset third field, or copying a position from a different tool with a different format.
Related errors
- failed to unmarshal Range from %q: missing End field
- failed to unmarshal Range from %q: missing Start field
- invalid gradient syntax
- no parameters in gradient
- no color stops in gradient
AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31).
Data as JSON: /api/errors/4feba30077a703ef.
Report an issue: GitHub.