dotnet/BenchmarkDotNet · error · JsonException

Unexpected token {reader.TokenType} when parsing double.

Error message

Unexpected token {reader.TokenType} when parsing double.

What it means

Thrown by SimpleJsonDoubleConverter.Read during JSON deserialization when the current token is not a JsonTokenType.Number. Similar to the float converter, the Write method encodes NaN/PositiveInfinity/NegativeInfinity as empty strings, but Read rejects all non-number tokens. Additionally, the Read method has a latent bug: it calls reader.GetSingle() (returning float) instead of reader.GetDouble(), which truncates precision on valid double values — this is a separate correctness issue beyond the exception itself.

Source

Thrown at src/BenchmarkDotNet/Serialization/BdnSimpleJsonSerializer.cs:75

        {
            if (float.IsNaN(value) || float.IsInfinity(value))
                writer.WriteStringValue("");
            else
                writer.WriteNumberValue(value);
        }
    }

    /// <summary>
    /// Custom JsonConverter for double that write Nan/PositiveInfinite/NegativeInfinite as empty string.
    /// </summary>
    private class SimpleJsonDoubleConverter : JsonConverter<double>
    {
        public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.Number)
                return reader.GetSingle();

            throw new JsonException($"Unexpected token {reader.TokenType} when parsing double.");
        }

        public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
        {
            if (double.IsNaN(value) || double.IsInfinity(value))
                writer.WriteStringValue("");
            else
                writer.WriteNumberValue(value);
        }
    }
}

View on GitHub (pinned to b515068b61)

Solutions

  1. For round-trip support, the Read method needs to handle string tokens (including empty string for NaN/Infinity) — this requires a library-level fix or custom converter.
  2. Ensure the input JSON encodes doubles as JSON numbers, not strings.
  3. Be aware of the GetSingle() vs GetDouble() precision bug: even when deserialization succeeds, doubles may lose precision. Report or patch this if high-precision values matter.
  4. Prefer the default System.Text.Json double handling for interop if BdnSimpleJsonSerializer's custom behavior is not required.

Example fix

// Current Read has two issues:
// 1. Calls GetSingle() instead of GetDouble() (precision loss)
// 2. Rejects string tokens that Write emits for NaN/Infinity

// A corrected Read:
public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    if (reader.TokenType == JsonTokenType.Number)
        return reader.GetDouble(); // fix: was GetSingle()
    if (reader.TokenType == JsonTokenType.String)
    {
        var s = reader.GetString();
        return string.IsNullOrEmpty(s) ? double.NaN : double.Parse(s);
    }
    throw new JsonException($"Unexpected token {reader.TokenType} when parsing double.");
}
Defensive patterns

Strategy: validation

Validate before calling

// Before deserializing, check for string-encoded doubles in the JSON
using var doc = JsonDocument.Parse(jsonString);
foreach (var prop in doc.RootElement.EnumerateObject())
    if (prop.Value.ValueKind == JsonValueKind.String && prop.Name.Contains("double", StringComparison.OrdinalIgnoreCase))
        throw new InvalidOperationException("JSON contains string-encoded double values that cannot be deserialized by BdnSimpleJsonSerializer.");

Try / catch

try
{
    var result = JsonSerializer.Deserialize<MyModel>(json, BdnSimpleJsonSerializer.Options);
}
catch (JsonException ex) when (ex.Message.Contains("parsing double"))
{
    // The double was serialized as a string (NaN/Infinity). Use default System.Text.Json options instead.
    var result = JsonSerializer.Deserialize<MyModel>(json);
}

Prevention

When it happens

Trigger: Deserializing JSON with double fields encoded as strings, null tokens, or the empty strings produced by Write for NaN/Infinity. The asymmetry between Write (emits empty string) and Read (rejects non-number) means JSON exported with this serializer cannot be safely re-imported if it contains NaN/Infinity.

Common situations: Exporting benchmark statistics (which frequently contain NaN for standard deviation of single-sample runs or Infinity in ratio calculations) to JSON and re-importing. Reading externally-produced JSON with string-encoded doubles.

Understand the failure class

Related errors


AI-assisted analysis of dotnet/BenchmarkDotNet@b515068b61 (2026-08-13). Data as JSON: /api/errors/3e432c007948fe29. Report an issue: GitHub.