cayleygraph/cayley · error

can not parse JSON-LD identifier: %#v

Error message

can not parse JSON-LD identifier: %#v

What it means

parseIdentifier is the last resort when converting a JSON-LD identifier string: it first tries parseBNode ("_:"-prefixed blank nodes) and then parseIRI (any other string is accepted as an IRI), so in practice this error fires when the input is not a string at all, or when the fallback path is handed an empty/invalid value. It signals that the value cannot be interpreted as any JSON-LD identifier form the registry supports.

Source

Thrown at query/linkedql/registry.go:263

		return "", fmt.Errorf("blank node ID must start with \"_:\"")
	}
	return quad.BNode(s[2:]), nil
}

func parseIRI(s string) (quad.IRI, error) {
	return quad.IRI(s), nil
}

func parseIdentifier(s string) (quad.Value, error) {
	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

View on GitHub (pinned to 81dcd7d73e)

Solutions

  1. Ensure the identifier is a string: either an absolute IRI ("http://...") or a blank node ("_:label").
  2. If the value is a node object, wrap it as {"@id": "<iri-or-bnode>"} so parseIdentifierString extracts the id first.
  3. Convert non-string scalars explicitly (fmt.Sprintf or a serializer) before placing them in identifier positions.
  4. Log %#v of the offending value and trace which step field supplied it.

Example fix

// before
{"@id": 42}
// after
{"@id": "http://example.com/entity/42"}
Defensive patterns

Strategy: type-guard

Validate before calling

func isUsableIdentifier(v interface{}) bool {
  switch t := v.(type) {
  case string:
    return t != "" && (strings.HasPrefix(t, "_:") || strings.Contains(t, ":"))
  case map[string]interface{}:
    _, ok := t["@id"].(string)
    return ok
  }
  return false
}

Type guard

func asIdentifier(v interface{}) (string, bool) {
  switch t := v.(type) {
  case string:
    return t, true
  case map[string]interface{}:
    id, ok := t["@id"].(string)
    return id, ok
  }
  return "", false
}

Try / catch

id, err := linkedql.BuildIdentifier(raw)
if err != nil {
  if strings.Contains(err.Error(), "can not parse JSON-LD identifier") {
    log.Printf("bad identifier %#[1]v in step field", raw)
    return fmt.Errorf("identifier field requires IRI or _:bnode string, got %T", raw)
  }
  return err
}

Prevention

When it happens

Trigger: BuildIdentifier or parseValue receiving a value that is neither a map with "@id" nor a usable string — e.g. a number, bool, nil, or array passed where an identifier (string or {"@id": ...}) is expected in a step document.

Common situations: Programmatic construction of LinkedQL steps where a variable of the wrong type is interpolated into an @id field; JSON decoding quirks where numbers or nulls end up in identifier positions; templating errors that render empty strings.

Related errors


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