iOfficeAI/OfficeCLI · error · System.ArgumentException
'runs' must be a JSON array of run objects.
Error message
'runs' must be a JSON array of run objects.
What it means
Thrown by ApplyRichTextToCell when the runs property parses as valid JSON but its root element is not an array. The code requires runs to be a JSON array of run objects (e.g. [{"text":"Hello","bold":"true"}]). A JSON object, string, or number at the root triggers this guard after JsonDocument.Parse succeeds but before array enumeration.
Source
Thrown at src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs:1440
SharedStringTable sst;
if (sstPart.SharedStringTable != null)
sst = sstPart.SharedStringTable;
else
{
sst = new SharedStringTable();
sstPart.SharedStringTable = sst;
}
var ssi = new SharedStringItem();
var gatheredRuns = new List<(string text, Dictionary<string, string> props)>();
if (properties.TryGetValue("runs", out var runsJson) && !string.IsNullOrWhiteSpace(runsJson))
{
try
{
using var jdoc = System.Text.Json.JsonDocument.Parse(runsJson);
if (jdoc.RootElement.ValueKind != System.Text.Json.JsonValueKind.Array)
throw new ArgumentException("'runs' must be a JSON array of run objects.");
foreach (var el in jdoc.RootElement.EnumerateArray())
{
if (el.ValueKind != System.Text.Json.JsonValueKind.Object)
throw new ArgumentException("Each run in 'runs' must be a JSON object.");
string text = "";
var pd = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var p in el.EnumerateObject())
{
var sv = p.Value.ValueKind switch
{
System.Text.Json.JsonValueKind.True => "true",
System.Text.Json.JsonValueKind.False => "false",
System.Text.Json.JsonValueKind.Null => "",
System.Text.Json.JsonValueKind.Number => p.Value.GetRawText(),
_ => p.Value.GetString() ?? ""
};
if (p.NameEquals("text")) text = sv;
else pd[p.Name] = sv;View on GitHub (pinned to 1ced45e900)
Solutions
- Wrap run objects in a JSON array: runs="[{\"text\":\"x\"}]".
- For a single run, still use the array form with one element.
- Use the legacy run1=text:prop=val syntax if building JSON arrays is inconvenient.
Example fix
// before
props["runs"] = "{\"text\":\"Hello\",\"bold\":\"true\"}";
// after
props["runs"] = "[{\"text\":\"Hello\",\"bold\":\"true\"}]"; Defensive patterns
Strategy: validation
Validate before calling
if (props.TryGetValue("runs", out var runsJson) && !string.IsNullOrWhiteSpace(runsJson))
{
using var jdoc = System.Text.Json.JsonDocument.Parse(runsJson);
if (jdoc.RootElement.ValueKind != System.Text.Json.JsonValueKind.Array)
throw new InvalidOperationException("runs must be a JSON array");
}
handler.Add(parentPath, "richtext", pos, props); Type guard
static bool RunsJsonIsArray(string? json)
{
if (string.IsNullOrWhiteSpace(json)) return true;
using var d = System.Text.Json.JsonDocument.Parse(json);
return d.RootElement.ValueKind == System.Text.Json.JsonValueKind.Array;
} Prevention
- Always serialize run collections with a JSON serializer that emits an array even for one element.
- Validate the runs JSON shape before passing it to Add/Set.
- Use the legacy run1= syntax for ad-hoc single runs to avoid JSON entirely.
When it happens
Trigger: properties["runs"]="{\"text\":\"x\"}" (a single object instead of an array), or runs="\"text\"" (a JSON string), or runs="42". Any valid JSON whose ValueKind is not Array. Malformed JSON hits error 494 instead.
Common situations: User wraps a single run in {} instead of []. JSON serializer configured to emit an object for a single-element collection. Copy-pasting a single run spec where an array is expected.
Related errors
- Each run in 'runs' must be a JSON object.
- Invalid JSON for 'runs': {jex.Message}
- Parent path must be /SheetName/CellRef for adding a run
- Anchor sheet '{aSegs[0]}' must match target sheet '{colSheet
- Invalid 'outline' value: '{addColOutline}'. Expected an inte
AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13).
Data as JSON: /api/errors/e870b2daaa529168.
Report an issue: GitHub.