{"record":{"id":"490d33e3cfad2543","repo":"microsoft/aspire","slug":"expected-a-json-array","errorCode":null,"errorMessage":"Expected a JSON array.","messagePattern":"Expected a JSON array\\.","errorType":"exception","errorClass":"JsonException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Dashboard/Model/GenAI/GenAIMessageParsingHelper.cs","lineNumber":36,"sourceCode":"    {\n        var bytes = Encoding.UTF8.GetBytes(json);\n        var readerOptions = new JsonReaderOptions\n        {\n            CommentHandling = JsonCommentHandling.Skip,\n            AllowTrailingCommas = true,\n        };\n        var reader = new Utf8JsonReader(bytes, readerOptions);\n        return DeserializeArrayIncrementally(ref reader, readElement);\n    }\n\n    internal static (List<T> items, bool truncated) DeserializeArrayIncrementally<T>(ref Utf8JsonReader reader, ReadElement<T> readElement)\n    {\n        var items = new List<T>();\n\n        // Read start of array. Let exceptions propagate for truly invalid JSON.\n        if (!reader.Read() || reader.TokenType != JsonTokenType.StartArray)\n        {\n            throw new JsonException(\"Expected a JSON array.\");\n        }\n\n        while (true)\n        {\n            bool readSuccess;\n            try\n            {\n                readSuccess = reader.Read();\n            }\n            catch (JsonException)\n            {\n                return (items, true);\n            }\n\n            if (!readSuccess)\n            {\n                return (items, true);\n            }","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Dashboard/Model/GenAI/GenAIMessageParsingHelper.cs#L18-L54","documentation":"DeserializeArrayIncrementally parses a JSON string as an array of T using Utf8JsonReader. If the first token is not the start of an array, a JsonException with this message is thrown; truly malformed JSON exceptions are intentionally allowed to propagate. This guards the incremental reader so it never loops over a non-array document.","triggerScenarios":"Calling DeserializeArrayIncrementally with input whose first JSON token is an object, string, number, or null instead of '[' — e.g. telemetry/store payloads where a single GenAI message object was saved without wrapping it in an array.","commonSituations":"Older OpenTelemetry GenAI telemetry stored a single chat message object rather than an array of messages; hand-edited or truncated JSON files in the telemetry store; producers changed shape between versions.","solutions":["Wrap the payload in an array before parsing: if the first non-whitespace char is '{', serialize as [payload].","For backwards compatibility, detect a leading '{' and fall back to parsing a single object then treat it as a one-element list.","Validate the stored JSON shape where it is written to prevent bad payloads from being persisted."],"exampleFix":"// before\nvar messages = GenAIMessageParsingHelper.DeserializeArrayIncrementally<ChatMessage>(json);\n// after\nvar trimmed = json.TrimStart();\nif (trimmed.StartsWith('{'))\n{\n    json = \"[\" + json + \"]\"; // older payloads stored a single message object\n}\nvar messages = GenAIMessageParsingHelper.DeserializeArrayIncrementally<ChatMessage>(json);","handlingStrategy":"try-catch","validationCode":"var trimmed = json.TrimStart();\nif (trimmed.Length == 0 || (trimmed[0] != '['))\n{\n    // normalize single-object payloads to arrays before parsing\n    if (trimmed.StartsWith('{')) json = \"[\" + json + \"]\";\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    items = GenAIMessageParsingHelper.DeserializeArrayIncrementally<T>(json);\n}\ncatch (JsonException ex)\n{\n    logger.LogWarning(ex, \"Payload is not a JSON array; skipping malformed telemetry entry\");\n}","preventionTips":["Always persist GenAI chat messages as a JSON array, even for a single message.","Validate payload shape at write time in the telemetry store.","Handle legacy single-object payloads with a normalization step."],"tags":["json","parsing","genai","telemetry","dashboard"],"backgroundTag":"unexpected-response-shape","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}