Skip to content

[API Proposal]: Built-in System.Text.Json converters for BFloat16 and Decimal32/64/128 #131097

Description

@tannergooding

Background and motivation

System.Text.Json ships built-in number converters for every primitive floating-point type plus Half (added in #87994). The remaining built-in floating-point types — System.Numerics.BFloat16 and the IEEE 754 decimal types Decimal32/Decimal64/Decimal128 (#81376) — have no built-in converter. They implement IFloatingPoint<T> exactly like float/double/Half, but today they cannot be (de)serialized as JSON numbers and require a hand-written JsonConverter<T> per type.

Source-generated contexts need a public JsonMetadataServices property per built-in type to emit (JsonMetadataServices.HalfConverter, etc.); there is no such property for these four, so both the reflection and source-gen resolvers fall through to unsupported-type handling.

This proposal adds the four missing properties, completing the built-in floating-point coverage. NFloat is intentionally excluded: it is a platform-dependent alias of float/double, so its JSON representation would vary by platform.

Related: #87994 (Half), #81376 (Decimal32/64/128).

API Proposal

namespace System.Text.Json.Serialization.Metadata;

public static partial class JsonMetadataServices
{
    public static JsonConverter<System.Numerics.BFloat16> BFloat16Converter { get; }
    public static JsonConverter<System.Numerics.Decimal32> Decimal32Converter { get; }
    public static JsonConverter<System.Numerics.Decimal64> Decimal64Converter { get; }
    public static JsonConverter<System.Numerics.Decimal128> Decimal128Converter { get; }
}

The properties are gated to the target framework where these types are available ($(NetCoreAppCurrent)), matching the existing #if-guarded built-in converter properties.

API Usage

using System.Numerics;
using System.Text.Json;

public class Measurement
{
    public BFloat16 Weight { get; set; }
    public Decimal128 Price { get; set; }
}

var m = new Measurement { Weight = (BFloat16)1.5f, Price = Decimal128.Parse("19.99") };

// Reflection-based: works via the default resolver.
string json = JsonSerializer.Serialize(m);       // {"Weight":1.5,"Price":19.99}
Measurement roundtrip = JsonSerializer.Deserialize<Measurement>(json);

Named literals and string number handling behave identically to the other floating-point converters:

var options = new JsonSerializerOptions
{
    NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
};

JsonSerializer.Serialize(Decimal32.NaN, options);              // "NaN"
JsonSerializer.Deserialize<Decimal128>("\"Infinity\"", options); // Decimal128.PositiveInfinity

Alternative Designs

  • Four separate converter types (one per type), mirroring HalfConverter. Rejected in the prototype in favor of a single internal generic converter constrained to IFloatingPointIeee754<T> (the interface exposing the NaN/Infinity members the converter needs) — all four types share the same parse/format/named-literal logic, so four near-identical files would be pure duplication. This is an implementation detail; the public surface is the four properties above either way.
  • Include NFloat. Rejected: NFloat is a platform-dependent alias, so serializing it as a JSON number produces platform-dependent output.

Risks

Adds four entries to the built-in simple-converter set, a small code-size/metadata increase for reflection and trimming scenarios on the target TFM. No binary or source breaking changes — these are pure additions gated behind the TFM where the underlying types exist. ApiCompat covers the binary surface.

Usage in dotnet/runtime

No adoption sites exist beyond the target library itself — these are foundational built-in converters registered by System.Text.Json's default resolvers, not APIs consumed by other runtime code. The prototype wires them into the reflection resolver (DefaultJsonTypeInfoResolver), the source generator (KnownTypeSymbols + JsonSourceGenerator.Parser), and adds reflection + source-gen round-trip tests.

Note

This proposal was drafted with GitHub Copilot.

Metadata

Metadata

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Text.JsonblockingMarks issues that we want to fast track in order to unblock other important work

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions