microsoft/aspire · error · JsonException

Expected property name.

Error message

Expected property name.

What it means

GenAIMessageParsingHelper.ReadChatMessage manually walks a GenAI message JSON object with Utf8JsonReader. After reading '{' or consuming a property value it expects the next token to be a property name; anything else means the JSON is not shaped like a chat message object, so it throws JsonException. It is a guard against malformed or non-object payloads being rendered in the GenAI visualizer.

Solutions

  1. Inspect the raw JSON stored in the telemetry entry and confirm each chat message is a JSON object like {"role":"user","parts":[...]}
  2. Upgrade the emitting instrumentation (e.g. OpenTelemetry .NET GenAI exporters) so messages follow the documented GenAI content schema
  3. Check for truncation of the recorded body (size limits when exporting) and re-export with larger limits
  4. Wrap parsing in DeserializeWithErrorHandling so a malformed message is surfaced as a description instead of a raw JsonException

Example fix

// before
var message = JsonDocument.Parse(rawBody).RootElement[0]; // an array, not an object
// after
if (message.ValueKind == JsonValueKind.Object && message.TryGetProperty("role", out _))
{
    var (role, parts, truncated) = GenAIMessageParsingHelper.ReadChatMessage(message);
}
Defensive patterns

Strategy: try-catch

Validate before calling

using var doc = JsonDocument.Parse(json);
var ok = doc.RootElement.ValueKind == JsonValueKind.Object &&
         doc.RootElement.TryGetProperty("role", out _);
if (!ok) throw new FormatException("Chat message is not a JSON object with a role property.");

Type guard

static bool IsChatMessageObject(JsonElement e) =>
    e.ValueKind == JsonValueKind.Object && e.TryGetProperty("role", out _);

Try / catch

try
{
    var (role, parts, truncated) = GenAIMessageParsingHelper.ReadChatMessage(element);
}
catch (JsonException ex)
{
    logger.LogWarning(ex, "Malformed GenAI chat message; rendering raw content instead.");
}

Prevention

When it happens

Trigger: Reading a telemetry GenAI chat message whose JSON at the current position is not an object with property names — e.g. the value of a message field is an array, string, or number instead of an object, or the JSON is truncated mid-structure so a non-property token appears where a name is expected.

Common situations: OTLP logs/traces where the GenAI message body was serialized by a different schema version or custom instrumentation; a user pasting/importing hand-edited telemetry JSON; truncated payloads saved from an export.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/5818d6b5ad86fda6. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Dashboard/Model/GenAI/GenAIMessageParsingHelper.cs:107

        if (reader.TokenType != JsonTokenType.StartObject)
        {
            throw new JsonException("Expected start of chat message object.");
        }

        string? role = null;
        List<MessagePart>? parts = null;
        var partsTruncated = false;

        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndObject)
            {
                break;
            }

            if (reader.TokenType != JsonTokenType.PropertyName)
            {
                throw new JsonException("Expected property name.");
            }

            var propertyName = reader.GetString();

            switch (propertyName)
            {
                case "role":
                    if (!reader.Read())
                    {
                        throw new JsonException("Unexpected end of JSON while reading role value.");
                    }
                    role = reader.GetString();
                    break;
                case "parts":
                    // DeserializeArrayIncrementally reads the StartArray token itself.
                    (parts, partsTruncated) = DeserializeArrayIncrementally<MessagePart>(ref reader, ReadMessagePart);
                    break;
                default:

View on GitHub (pinned to 25830f84bd)