{"record":{"id":"50724694e690427d","repo":"iOfficeAI/OfficeCLI","slug":"expected-object-or-key-value-array-for-props","errorCode":null,"errorMessage":"Expected object or [\"key=value\"] array for props","messagePattern":"Expected object or \\[\"key=value\"\\] array for props","errorType":"validation","errorClass":"JsonException","httpStatus":null,"severity":"warning","filePath":"src/officecli/BatchTypes.cs","lineNumber":36,"sourceCode":"        // item. Before this, batch props was object-only and every array-form\n        // batch failed with \"Expected object for props\" — observed as a 100%\n        // batch-failure for models that (correctly) reused the single-command\n        // props shape. Lenient split on the first '=' matches McpServer.ParseProps.\n        if (reader.TokenType == JsonTokenType.StartArray)\n        {\n            while (reader.Read())\n            {\n                if (reader.TokenType == JsonTokenType.EndArray) return dict;\n                if (reader.TokenType != JsonTokenType.String)\n                    throw new JsonException(\"Expected \\\"key=value\\\" string in props array\");\n                var kv = reader.GetString()!;\n                var eq = kv.IndexOf('=');\n                if (eq > 0) dict[kv[..eq]] = kv[(eq + 1)..];  // skip malformed, as ParseProps does\n            }\n            throw new JsonException(\"Unexpected end of JSON\");\n        }\n        if (reader.TokenType != JsonTokenType.StartObject)\n            throw new JsonException(\"Expected object or [\\\"key=value\\\"] array for props\");\n        while (reader.Read())\n        {\n            if (reader.TokenType == JsonTokenType.EndObject) return dict;\n            if (reader.TokenType != JsonTokenType.PropertyName)\n                throw new JsonException(\"Expected property name\");\n            var key = reader.GetString()!;\n            reader.Read();\n            var value = reader.TokenType switch\n            {\n                JsonTokenType.String => reader.GetString()!,\n                JsonTokenType.Number => reader.TryGetInt64(out var l) ? l.ToString() : reader.GetDouble().ToString(),\n                JsonTokenType.True => \"true\",\n                JsonTokenType.False => \"false\",\n                JsonTokenType.Null => \"\",\n                _ => throw new JsonException($\"Unexpected token {reader.TokenType} for prop value '{key}'\")\n            };\n            dict[key] = value;\n        }","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/BatchTypes.cs#L18-L54","documentation":"JsonException from LenientStringDictionaryConverter.Read when the props value is neither an array nor an object — a bare scalar at the props position (number, boolean, string, or a top-level null that wasn't handled). The converter accepts only StartArray or StartObject, so any other leading token is rejected.","triggerScenarios":"\"props\": 42, \"props\": true, \"props\": \"text=hi\" (a string, not an array/object), or \"props\": <some other scalar>. A literal null is returned as null before this check only when it is the leading token.","commonSituations":"An agent that supplies props as a single string instead of an object/array; a numeric flag mistakenly assigned to props; a schema drift where props became a scalar.","solutions":["Provide props as an object {key:'value'} or an array [\"key=value\"].","If you only have one kv-pair as a string, wrap it: props: ['text=hi'] or props: { text: 'hi' }.","Omit props entirely when there are none, rather than sending a scalar."],"exampleFix":"// before: { command:'set', props: 'text=hi' }\n// after:  { command:'set', props: ['text=hi'] }  // or props: { text: 'hi' }","handlingStrategy":"type-guard","validationCode":"// Ensure props is an object or a string array before sending\nfunction validProps(p) {\n  if (p == null) return true;\n  if (Array.isArray(p)) return p.every(x => typeof x === 'string');\n  return typeof p === 'object';\n}","typeGuard":"function isPropsShape(p) {\n  if (p == null) return true;\n  if (Array.isArray(p)) return p.every(x => typeof x === 'string');\n  return p !== null && typeof p === 'object';\n}","tryCatchPattern":null,"preventionTips":["Never put a scalar (number/boolean/string) at the props position.","Omit props when empty rather than sending null/scalar.","Validate item shape with isPropsShape() in test builds."],"tags":["batch","json","props","deserialization","api-contract"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}