Skip to content

DataModel.update does not parse adjacency-list contents for non-root paths #710

@ymmt2005

Description

@ymmt2005

Describe the bug

DataModel.update only calls _parseDataModelContents when the path is root (/ or empty). For non-root paths (e.g. /todos), the raw contents List is passed directly to _updateValue, which stores it as-is. This causes a TypeError when a widget subscribes to that path expecting Map<String, Object?> — because the stored value is still the raw List<dynamic> (the A2UI adjacency-list format).

The error manifests as:

type 'List<dynamic>' is not a subtype of type 'Map<String, Object?>?' in type cast

thrown from DataModel.getValue (data_model.dart:190) when ComponentChildrenBuilder subscribes to a template's dataBinding path.

To Reproduce

  1. Send a dataModelUpdate A2UI message with a non-root path (e.g. "/todos") and contents in the standard adjacency-list format:
{
  "dataModelUpdate": {
    "surfaceId": "todo-main",
    "path": "/todos",
    "contents": [
      {"key": "0", "valueMap": [
        {"key": "id", "valueString": "abc"},
        {"key": "title", "valueString": "Buy groceries"}
      ]},
      {"key": "1", "valueMap": [
        {"key": "id", "valueString": "def"},
        {"key": "title", "valueString": "Finish report"}
      ]}
    ]
  }
}
  1. Have a List component with a template bound to /todos:
{
  "List": {
    "children": {
      "template": {
        "dataBinding": "/todos",
        "componentId": "item-template"
      }
    }
  }
}
  1. The app crashes with type 'List<dynamic>' is not a subtype of type 'Map<String, Object?>?' in type cast.

Expected behavior

DataModel.update should parse the adjacency-list contents into a Map<String, Object?> for all paths, not just the root. The relevant code in data_model.dart:

void update(DataPath? absolutePath, Object? contents) {
  if (absolutePath == null || absolutePath.segments.isEmpty) {
    if (contents is List) {
      _data = _parseDataModelContents(contents); // ← only called for root
    }
    // ...
    return;
  }
  _updateValue(_data, absolutePath.segments, contents); // ← raw List stored as-is
}

For non-root paths, contents should also be parsed through _parseDataModelContents before being stored, e.g.:

void update(DataPath? absolutePath, Object? contents) {
  Object? parsedContents = contents;
  if (contents is List) {
    parsedContents = _parseDataModelContents(contents);
  }
  if (absolutePath == null || absolutePath.segments.isEmpty) {
    if (parsedContents is Map) {
      _data = Map<String, Object?>.from(parsedContents);
    }
    // ...
    return;
  }
  _updateValue(_data, absolutePath.segments, parsedContents);
}

Additional context

  • Package version: genui 0.7.0
  • The A2UI v0.8 specification defines dataModelUpdate.contents as an adjacency-list array regardless of whether path is specified.
  • ComponentChildrenBuilder (widget_helpers.dart:103) subscribes with subscribe<Map<String, Object?>>, which calls getValue<Map<String, Object?>>_getValue(...) as Map<String, Object?>?, triggering the cast error.
  • Workaround: intercept DataModelUpdate messages at the ContentGenerator stream level and pre-parse contents from the adjacency-list to a plain Map before the SDK processes them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions