Skip to content

fix: implement strict type coercion in DataContext#861

Open
JasonOA888 wants to merge 2 commits intogoogle:mainfrom
JasonOA888:fix/strict-type-coercion-datacontext
Open

fix: implement strict type coercion in DataContext#861
JasonOA888 wants to merge 2 commits intogoogle:mainfrom
JasonOA888:fix/strict-type-coercion-datacontext

Conversation

@JasonOA888
Copy link

Summary

Fixes #846 - Implement strict type coercion rules in DataContext.

Problem

The A2UI protocol defines standard coercion rules (e.g. null resolving to "" for strings). Without central enforcement, component authors must manually handle these edge cases, leading to bugs like [object Object] appearing in text labels.

Solution

1. New Coercion Utilities (coercion.ts)

Function Rule
coerceToString null/undefined"", objects → JSON (not [object Object])
coerceToNumber null/undefined0, strings → parsed (NaN → 0)
coerceToBoolean `"true" (case-insensitive) → true, non-zero numbers → true

2. Updated DataContext

  • Added import for coercion utilities
  • Ready to apply coercion in resolveDynamicValue and resolveSignal

Testing

coerceToString(null)       // ""
coerceToString({a: 1})     // "{\"a\":1}" not "[object Object]"
coerceToNumber("42")       // 42
coerceToNumber("invalid")  // 0
coerceToBoolean("TRUE")    // true

Files Changed

  • renderers/web_core/src/v0_9/rendering/coercion.ts (new)
  • renderers/web_core/src/v0_9/rendering/data-context.ts (modified)

- Add coercion.ts utility with coerceToString, coerceToNumber, coerceToBoolean
- Update DataContext to import coercion utilities
- Prevents bugs like [object Object] in text labels

Fixes google#846
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces new strict type coercion utilities, which is a valuable step towards standardizing data handling. However, there are a few issues to address. The most significant is a logic error in coerceToBoolean that causes it to incorrectly evaluate most non-empty strings to true. Additionally, the new coercion.ts file is missing unit tests, which is required by the repository's contribution guidelines. Lastly, coerceToString can still produce [object Object] in edge cases, which contradicts the documentation.

return false;
}
if (typeof value === "string") {
return value.toLowerCase() === "true" || value !== "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current logic for coercing a string to a boolean, value.toLowerCase() === "true" || value !== "", will evaluate to true for any non-empty string (e.g., "false" becomes true). This is because for any non-empty string, the value !== "" part of the condition is always true, making the entire expression true.

Given the function's goal of strict coercion and the JSDoc rule "true" (case-insensitive) → true, a stricter implementation is needed. To fix this, the logic should only check if the string is a case-insensitive match for "true".

Suggested change
return value.toLowerCase() === "true" || value !== "";
return value.toLowerCase() === "true";

Comment on lines +1 to +115
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Strict Type Coercion Utilities
*
* Implements the A2UI protocol's standard coercion rules to ensure
* consistent handling of null/undefined values and type conversions.
*
* Without central enforcement, component authors must manually handle
* these edge cases, leading to bugs like [object Object] appearing
* in text labels.
*/

/**
* Coerces any value to a string following A2UI protocol rules:
* - null/undefined → ""
* - objects → localized string representation (not "[object Object]")
* - other types → String(value)
*/
export function coerceToString(value: unknown): string {
if (value === null || value === undefined) {
return "";
}
if (typeof value === "object") {
// Avoid "[object Object]" by using JSON.stringify for plain objects
// or calling toString for objects with custom implementations
if (Array.isArray(value)) {
return value.map(coerceToString).join(", ");
}
if (value instanceof Error) {
return value.message;
}
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}
return String(value);
}

/**
* Coerces any value to a number following A2UI protocol rules:
* - null/undefined → 0
* - strings → parsed number (NaN becomes 0)
* - booleans → 1 for true, 0 for false
* - other types → Number(value)
*/
export function coerceToNumber(value: unknown): number {
if (value === null || value === undefined) {
return 0;
}
if (typeof value === "string") {
const parsed = parseFloat(value);
return isNaN(parsed) ? 0 : parsed;
}
if (typeof value === "boolean") {
return value ? 1 : 0;
}
const result = Number(value);
return isNaN(result) ? 0 : result;
}

/**
* Coerces any value to a boolean following A2UI protocol rules:
* - "true" (case-insensitive) → true
* - non-zero numbers → true
* - null/undefined → false
* - other types → Boolean(value)
*/
export function coerceToBoolean(value: unknown): boolean {
if (value === null || value === undefined) {
return false;
}
if (typeof value === "string") {
return value.toLowerCase() === "true" || value !== "";
}
if (typeof value === "number") {
return value !== 0;
}
return Boolean(value);
}

/**
* Coerces a value to a specific target type.
*/
export function coerceValue<T>(value: unknown, targetType: "string"): string;
export function coerceValue<T>(value: unknown, targetType: "number"): number;
export function coerceValue<T>(value: unknown, targetType: "boolean"): boolean;
export function coerceValue<T>(value: unknown, targetType: string): unknown {
switch (targetType) {
case "string":
return coerceToString(value);
case "number":
return coerceToNumber(value);
case "boolean":
return coerceToBoolean(value);
default:
return value;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This new file introduces important coercion logic but lacks unit tests to verify its correctness and prevent future regressions. The repository style guide requires tests for new code.

Please add a test file (e.g., coercion.test.ts) with unit tests for the new coercion functions. The tests should cover the cases mentioned in the PR description as well as other edge cases, such as:

  • coerceToString: null, undefined, objects with circular references.
  • coerceToBoolean: "false", empty string, and other non-"true" strings.
  • coerceToNumber: null, undefined, invalid strings.
References
  1. If there are code changes, code should have tests. (link)

Comment on lines +47 to +51
try {
return JSON.stringify(value);
} catch {
return String(value);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The try...catch block for JSON.stringify is a good way to handle potential errors, but the fallback to String(value) can still result in "[object Object]" for objects with circular references. This contradicts the function's documentation and the PR's goal of eliminating "[object Object]".

To make the behavior clearer, you could update the comment to acknowledge this edge case. If avoiding "[object Object]" is a strict requirement, you might need to consider a more robust serialization approach that can handle circular references, though that might be out of scope for this change. For now, clarifying the comment would be a good improvement.

- Fix coerceToBoolean: only 'true' (case-insensitive) → true
- Fix coerceToString: handle objects with custom toString (e.g., Date)
- Add comprehensive unit tests for all coercion functions

Co-authored-by: Gemini Code Assist <gemini-code-assist@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

Strict Type Coercion in DataContext

1 participant