Search code examples
asp.net-coreasp.net-core-webapiodata

IEEE754Compatible=true doesn't return int64s in string format in ASP.NET Core OData 8


According to the spec, OData v4 services should return int64 and decimal types in string format when IEEE754Compatible=true is included in the Content-Type request header in the Accept request header or the $format query string parameter.

Here are the relevant sections:

3 Requesting the JSON Format
The OData JSON format can be requested using the $format query option in the request URL with the media type application/json, optionally followed by format parameters, or the case-insensitive abbreviation json which MUST NOT be followed by format parameters.

Alternatively, this format can be requested using the Accept header with the media type application/json, optionally followed by format parameters.

If specified, $format overrides any value specified in the Accept header.

Possible format parameters are:

  • ExponentialDecimals
  • IEEE754Compatible
  • metadata (odata.metadata)
  • streaming (odata.streaming)

3.2 Controlling the Representation of Numbers
The IEEE754Compatible=true format parameter indicates that the service MUST serialize Edm.Int64 and Edm.Decimal numbers (including the count, if requested) as strings. This is in conformance with [RFC7493].

If not specified, or specified as IEEE754Compatible=false, all numbers MUST be serialized as JSON numbers.

This enables support for JavaScript numbers that are defined to be 64-bit binary format IEEE 754 values [ECMAScript] (see section 4.3.1.9) resulting in integers losing precision past 15 digits, and decimals losing precision due to the conversion from base 10 to base 2.

This doesn't happen for me when using ASP.Net Core OData 8 (even though there are discussions on Github c.2016 where it appears to have been implemented in OData.net: e.g., here).

As a minimal reproducible example:

  • I created a new project exactly as described in Microsoft's Getting Started with ASP.NET Core OData 8 tutorial.
  • I added a new property TestLong with type long to the Customer model
  • I initialised the property in the test Customers with long.MaxValue

Yet, I receive number formatted values in the JSON, instead of string.

Using the Accept header: A Postman request to the OData v4 service, including the parameter IEEE754Compatible=true in the Accept request header. The response is shown to have returned int64 formatted instead of string formatted values.

Using the $format query: A Postman request to the OData v4 service, including the parameter IEEE754Compatible=true in the $format query. The response is shown to have returned int64 formatted instead of string formatted values.

UPDATE

Following the comment from @Jonathan Alfaro, I investigated to try and find where the IsIeee754Compatible parameter is actually set. (No luck so far).

I created a new jsonwriter factory as described in this dev blog:

using Microsoft.OData.Json;
using System.Text;

namespace TestIEEE754Compatibility.Models
{
    public class CustomStreamBasedJsonWriterFactory : IStreamBasedJsonWriterFactory
    {
        public IJsonWriterAsync CreateAsynchronousJsonWriter(Stream stream, bool isIeee754Compatible, Encoding encoding)
        {
            return DefaultStreamBasedJsonWriterFactory.Default.CreateAsynchronousJsonWriter(stream, isIeee754Compatible, encoding);
        }

        public IJsonWriter CreateJsonWriter(Stream stream, bool isIeee754Compatible, Encoding encoding)
        {
            return DefaultStreamBasedJsonWriterFactory.Default.CreateJsonWriter(stream, isIeee754Compatible, encoding);
        }
    }
}

Which I injected as below:

var edmModel = modelBuilder.GetEdmModel();

builder.Services.AddControllers().AddOData(
    options => {
        options.Select().Filter().OrderBy().Expand().Count().SetMaxTop(null).AddRouteComponents(
            "odata",
            edmModel,
            services =>
            {
                services.AddSingleton<IStreamBasedJsonWriterFactory>(o => new CustomStreamBasedJsonWriterFactory());
            });
    });

And the isIeee754Compatible parameter is being passed false even though the IEEE754Compatible=true parameter is present (see screenshot below). So, clearly it doesn't seem to be set automatically. But I can't for the life of me find out where it can be set manually.

A screenshot of a debugger stepping through the CreateAsynchronousJsonWriter method of CustomAsynchronousJsonWriter, showing that the isIeee754Compatible parameter is passed false even when IEEE754Compatible=true is passed in the request


Solution

  • This wasn't fully implemented in ASP.NET Core OData 8. I raised an issue on GitHub and it was quickly resolved.

    The GitHub issue Feature is merged into main

    I've confirmed using nightly build 8.2.3-Nightly202312181316 that it is resolved. You just need to add IEEE754Compatible=true to either the Accept request header, or the $format query. There are no other changes you need to make in your code, OData handles it internally.

    Accept header: Postman request showing a request to the OData endpoint including IEEE754Compatible=true in the Accept request header returns int64 types in string format.

    Format query: Postman request showing a request to the OData endpoint including IEEE754Compatible=true in the $format query returns int64 types in string format.