cayleygraph/cayley · error

expected a @id key

Error message

expected a @id key

What it means

After confirming the input is a map, parseIdentifierString reads m["@id"] and asserts it is a string. If the map has no "@id" key — or "@id" holds a non-string value like a number or nested object — this error is returned. JSON-LD node objects used as identifiers must carry a string "@id" member.

Source

Thrown at query/linkedql/registry.go:273

	bnode, err := parseBNode(s)
	if err == nil {
		return bnode, nil
	}
	iri, err := parseIRI(s)
	if err == nil {
		return iri, nil
	}
	return nil, fmt.Errorf("can not parse JSON-LD identifier: %#v", s)
}

func parseIdentifierString(a interface{}) (string, error) {
	m, ok := a.(map[string]interface{})
	if !ok {
		return "", fmt.Errorf("unexpected type: %T", a)
	}
	id, ok := m["@id"].(string)
	if !ok {
		return "", fmt.Errorf("expected a @id key")
	}
	return id, nil
}

func parseLiteral(a interface{}) (quad.Value, error) {
	switch a := a.(type) {
	case string:
		return quad.String(a), nil
	case int64:
		return quad.Int(a), nil
	case float64:
		i := int64(a)
		if a == float64(i) {
			return quad.Int(i), nil
		}
		return quad.Float(a), nil
	case bool:
		return quad.Bool(a), nil

View on GitHub (pinned to 81dcd7d73e)

Solutions

  1. Add a string "@id" key to the node object: {"@id": "http://example.com/x"}.
  2. Fix the key spelling/casing — it must be exactly "@id".
  3. If the object is a literal, ensure it flows to a literal-accepting field (parseLiteral) rather than an identifier field.
  4. Ensure "@id"'s value is a plain JSON string, not a number or nested object.

Example fix

// before
{"type": "Person", "name": "Alice"}
// after
{"@id": "http://example.com/people/alice", "type": "Person", "name": "Alice"}
Defensive patterns

Strategy: validation

Validate before calling

func hasStringID(m map[string]interface{}) bool {
  id, ok := m["@id"]
  if !ok { return false }
  s, ok := id.(string)
  return ok && s != ""
}
// if !hasStringID(node) { return errors.New("node object requires a string @id") }

Type guard

func extractID(v interface{}) (string, bool) {
  m, ok := v.(map[string]interface{})
  if !ok { return "", false }
  s, ok := m["@id"].(string)
  return s, ok && s != ""
}

Try / catch

id, err := parseIdentifierString(node)
if err != nil && err.Error() == "expected a @id key" {
  return fmt.Errorf("node %v is a value/literal object, not an identifier: add a string @id", node)
}

Prevention

When it happens

Trigger: Passing a node object without "@id" (e.g. {"@value": "x"} literal form, or {"type": "Person"}) or with a non-string "@id" (e.g. {"@id": 123}) into parseValue/BuildIdentifier paths.

Common situations: Using JSON-LD value objects ({"@value"/"@language"/"@type"}) where identifiers are required; documents produced by other JSON-LD tooling that emit @id as a nested structure; typos like "ID" or "id" instead of "@id".

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of cayleygraph/cayley@81dcd7d73e (2026-09-06). Data as JSON: /api/errors/9952068066be0827. Report an issue: GitHub.