Skip to content

UpdateDataModel doesn't distinguish between the value-omitted and value-null cases #935

@andrewkolos

Description

@andrewkolos

The v0.9 spec distinguishes two intents on updateDataModel:

  • "value": <x> (key is present): replace the value at path with x.
  • value key absent: remove the key at path.

UpdateDataModel.fromJson collapses both into a single Object? value field:

/// Creates a [UpdateDataModel] message from a JSON map.
factory UpdateDataModel.fromJson(JsonMap json) {
return UpdateDataModel(
surfaceId: json[surfaceIdKey] as String,
path: DataPath(json['path'] as String? ?? '/'),
value: json['value'],
);
}

and toJson() drops the key with 'value': ?value. The wire-level distinction is lost as soon as the message hits the model.

/// Converts this message to a JSON map.
Map<String, dynamic> toJson() => {
'version': 'v0.9',
surfaceIdKey: surfaceId,
'path': path.toString(),
'value': ?value,
};

Expressed in a code example:

final message = UpdateDataModel.fromJson({
  'surfaceId': 's1',
  'path': '/x',
  'value': null,
});
final serialized = message.toJson();
expect(serialized.containsKey('value'), isTrue); // fails. value key is dropped.

A deeper problem is that the data model also can't distinguish between "set to null" and "remove". There's no method to set a key to null. Source:

final model = InMemoryDataModel();
model.update(DataPath.root, {'x': 1, 'y': 2});
model.update(DataPath('/x'), null);
// Expected per 0.9 spec: {'x': null, 'y': 2}
// Actual: {'y': 2}

So even if UpdateDataModel.fromJson parsed the distinction correctly, the data model can't set a field to null:

if (current is Map) {
if (remaining.isEmpty) {
if (value == null) {
current.remove(segment);
} else {
current[segment] = value;
}
return;
}

I think the most practical impact of this issue would manifest in UIs that use tri-state fields (e.g. false, true, unset/reset). Not very common, but worth fixing in my opinon. To fix, I suggest splitting the setter API into a setter and a remover, then have MessageProcessor branch on hasValue.

Metadata

Metadata

Assignees

Labels

front-line-handledCan wait until the second-line triage. The front-line triage already checked if it's a P0.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions