iOfficeAI/OfficeCLI · error · ArgumentException

comment runs: value must be a JSON array

Error message

comment runs: value must be a JSON array

What it means

Thrown by BuildCommentTextFromRuns when JsonNode.Parse succeeds but the result is not a JsonArray (e.g. it parsed as a JSON object, string, number, or scalar). The runs contract requires an array of run objects.

Source

Thrown at src/officecli/Handlers/Excel/ExcelHandler.Helpers.Node.cs:982

        }
        return arr.ToJsonString();
    }

    // Build a CommentText from a `runs=<json array>` value, one <r> per run
    // carrying its own <rPr>. Run vocabulary mirrors rich-text cells
    // (bold/italic/strike/underline/superscript/subscript/size/color/font) so
    // the two paths share one input contract. Comment runs keep the Tahoma-9
    // indexed-81 default for facets a run leaves unspecified.
    internal static CommentText BuildCommentTextFromRuns(string runsJson)
    {
        System.Text.Json.Nodes.JsonArray? arr;
        try { arr = System.Text.Json.Nodes.JsonNode.Parse(runsJson) as System.Text.Json.Nodes.JsonArray; }
        catch (System.Text.Json.JsonException ex)
        {
            throw new ArgumentException($"comment runs: invalid JSON array — {ex.Message}");
        }
        if (arr == null)
            throw new ArgumentException("comment runs: value must be a JSON array");

        var ct = new CommentText();
        foreach (var item in arr)
        {
            if (item is not System.Text.Json.Nodes.JsonObject o) continue;
            var text = o["text"]?.GetValue<string>() ?? "";
            OfficeCli.Core.ParseHelpers.ValidateXmlText(text, "comment run text");

            var rPr = new RunProperties();
            bool RunBool(string key) => o[key] is { } n
                && (n.GetValueKind() == System.Text.Json.JsonValueKind.True
                    || (n.GetValueKind() == System.Text.Json.JsonValueKind.String && IsTruthy(n.GetValue<string>())));

            if (RunBool("bold")) rPr.AppendChild(new Bold());
            if (RunBool("italic")) rPr.AppendChild(new Italic());
            if (RunBool("strike")) rPr.AppendChild(new Strike());
            var uStr = o["underline"]?.GetValue<string>();
            if (!string.IsNullOrEmpty(uStr) && !string.Equals(uStr, "none", StringComparison.OrdinalIgnoreCase))

View on GitHub (pinned to 1ced45e900)

Solutions

  1. Wrap the value in an array: runs='[{...}]' even for a single run.
  2. Validate ValueKind == Array before sending (see validationCode).
  3. Re-check the schema example — runs is always a top-level JSON array, each element an object with at least a 'text' key.

Example fix

// before
string runs = "{\"text\":\"hi\"}"; // object, not array
// after
string runs = "[{\"text\":\"hi\"}]"; // array of one run
Defensive patterns

Strategy: validation

Validate before calling

static bool IsRunsArray(string runsJson)
{
    try
    {
        using var doc = System.Text.Json.JsonDocument.Parse(runsJson);
        return doc.RootElement.ValueKind == System.Text.Json.JsonValueKind.Array;
    }
    catch { return false; }
}

Type guard

null

Try / catch

try { BuildCommentTextFromRuns(runs); }
catch (ArgumentException ex) when (ex.Message == "comment runs: value must be a JSON array")
{ /* wrap single object into array, then retry */ }

Prevention

When it happens

Trigger: runs='{"text":"hi"}' (a JSON object, not array), runs="\"hello\"" (a JSON string), runs='42' (a JSON number), or runs='true'. All parse fine but cast to JsonArray yields null.

Common situations: Passing a single run object instead of a one-element array; reusing a schema that wraps runs in an object; sending the text payload directly instead of an array wrapper.

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/61b7a69832e069a3. Report an issue: GitHub.