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

Labels

P1A high-priority issue. Someone should be assigned and actively working on it.front-line-handledCan wait until the second-line triage. The front-line triage already checked if it's a P0.qualityAn issue which affects the quality of the Gen UI productsecond-line-triagedThe issue has been fully triaged.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions