iOfficeAI/OfficeCLI · warning · JsonException

Expected "key=value" string in props array

Error message

Expected "key=value" string in props array

What it means

JsonException from LenientStringDictionaryConverter.Read while parsing a `props` value given as an array (the ["key=value", ...] form). Each element must be a JSON string; this throws when an element is a number, boolean, object, or nested array. It mirrors McpServer.ParseProps, which only accepts string kv-pairs in the array form.

Source

Thrown at src/officecli/BatchTypes.cs:28

{
    public override Dictionary<string, string>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.Null) return null;
        var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        // Array form: ["key=value", ...]. This mirrors the single-command MCP
        // `props` argument and the CLI `--prop key=value` flag, so an agent that
        // learned props from `set`/`add` produces the same shape inside a batch
        // item. Before this, batch props was object-only and every array-form
        // batch failed with "Expected object for props" — observed as a 100%
        // batch-failure for models that (correctly) reused the single-command
        // props shape. Lenient split on the first '=' matches McpServer.ParseProps.
        if (reader.TokenType == JsonTokenType.StartArray)
        {
            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.EndArray) return dict;
                if (reader.TokenType != JsonTokenType.String)
                    throw new JsonException("Expected \"key=value\" string in props array");
                var kv = reader.GetString()!;
                var eq = kv.IndexOf('=');
                if (eq > 0) dict[kv[..eq]] = kv[(eq + 1)..];  // skip malformed, as ParseProps does
            }
            throw new JsonException("Unexpected end of JSON");
        }
        if (reader.TokenType != JsonTokenType.StartObject)
            throw new JsonException("Expected object or [\"key=value\"] array for props");
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndObject) return dict;
            if (reader.TokenType != JsonTokenType.PropertyName)
                throw new JsonException("Expected property name");
            var key = reader.GetString()!;
            reader.Read();
            var value = reader.TokenType switch
            {
                JsonTokenType.String => reader.GetString()!,

View on GitHub (pinned to 1ced45e900)

Solutions

  1. Make every props-array element a "key=value" string: props: ["text=hi", "count=42"].
  2. If you have rich values, switch to the object form: props: { text: 'hi', count: '42' }.
  3. Validate/normalize props to strings before serializing the batch.

Example fix

// before: { command:'set', props: ['text=hi', 42] }
// after:  { command:'set', props: ['text=hi', 'count=42'] }  // or props: { text:'hi', count:'42' }
Defensive patterns

Strategy: validation

Validate before calling

// Normalize props to the accepted shapes before serializing a batch
function normalizeProps(props) {
  if (props == null) return undefined;
  if (Array.isArray(props)) return props.map(v => typeof v === 'string' ? v : null).filter(Boolean); // drop non-strings
  if (typeof props === 'object') return props;
  return undefined;
}

Type guard

// Accept only string elements in a props array
function isStringArray(a) { return Array.isArray(a) && a.every(x => typeof x === 'string'); }

Prevention

When it happens

Trigger: A batch item with "props": ["text=hi", 42] or "props": ["text=hi", {"x":1}] or "props": [true]. Any non-string token inside the props array.

Common situations: An agent/model that emits props as a mixed array after learning a numeric value; serializing a Map/struct directly into the array; hand-built JSON with an unquoted value.

Related errors


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