Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ValueTuple is not Supported in Aspnet Core 3.1 with default Json. #1519

Closed
newpost opened this issue Jan 9, 2020 · 3 comments
Closed

ValueTuple is not Supported in Aspnet Core 3.1 with default Json. #1519

newpost opened this issue Jan 9, 2020 · 3 comments

Comments

@newpost
Copy link

newpost commented Jan 9, 2020

[ApiController]
    [Route("[controller]")]
    public class DefaultController : ControllerBase
    {
        [HttpGet]
        public ValueTuple<int, int, int> Index()
        {
            return new ValueTuple<int,int,int>(1,2,3);
        }
    }

actual output: {}.
expected output:{"item1":1,"item2":2,"item3":3}

@rynowak rynowak transferred this issue from dotnet/aspnetcore Jan 9, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Jan 9, 2020
@rynowak
Copy link
Member

rynowak commented Jan 9, 2020

/cc @ahsonkhan - feedback about JSON.

@Gnbrkm41
Copy link
Contributor

Gnbrkm41 commented Jan 9, 2020

Duplicate of #876?

@ahsonkhan
Copy link
Member

ahsonkhan commented Jan 9, 2020

This is a duplicate of #876.

ValueTuples are currently not supported by System.Text.Json since they require field support and System.Text.Json only supports public properties currently.

Other similar issues:
https://github.com/dotnet/corefx/issues/41420
https://github.com/dotnet/corefx/issues/36240
#552

Here's a potential workaround in the mean time using a custom converter:

public class ValueTupleFactory : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
    {
        Type iTuple = typeToConvert.GetInterface("System.Runtime.CompilerServices.ITuple");
        return iTuple != null;
    }

    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        Type[] genericArguments = typeToConvert.GetGenericArguments();

        Type converterType = genericArguments.Length switch
        {
            1 => typeof(ValueTupleConverter<>).MakeGenericType(genericArguments),
            2 => typeof(ValueTupleConverter<,>).MakeGenericType(genericArguments),
            3 => typeof(ValueTupleConverter<,,>).MakeGenericType(genericArguments),
            // And add other cases as needed
            _ => throw new NotSupportedException(),
        };
        return (JsonConverter)Activator.CreateInstance(converterType);
    }
}

public class ValueTupleConverter<T1> : JsonConverter<ValueTuple<T1>>
{
    public override ValueTuple<T1> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        ValueTuple<T1> result = default;

        if (!reader.Read())
        {
            throw new JsonException();
        }

        while (reader.TokenType != JsonTokenType.EndObject)
        {
            if (reader.ValueTextEquals("Item1") && reader.Read())
            {
                result.Item1 = JsonSerializer.Deserialize<T1>(ref reader, options);
            }
            else
            {
                throw new JsonException();
            }
            reader.Read();
        }

        return result;
    }

    public override void Write(Utf8JsonWriter writer, ValueTuple<T1> value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WritePropertyName("Item1");
        JsonSerializer.Serialize<T1>(writer, value.Item1, options);
        writer.WriteEndObject();
    }
}

public class ValueTupleConverter<T1, T2> : JsonConverter<ValueTuple<T1, T2>>
{
    public override (T1, T2) Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        (T1, T2) result = default;

        if (!reader.Read())
        {
            throw new JsonException();
        }

        while (reader.TokenType != JsonTokenType.EndObject)
        {
            if (reader.ValueTextEquals("Item1") && reader.Read())
            {
                result.Item1 = JsonSerializer.Deserialize<T1>(ref reader, options);
            }
            else if (reader.ValueTextEquals("Item2") && reader.Read())
            {
                result.Item2 = JsonSerializer.Deserialize<T2>(ref reader, options);
            }
            else
            {
                throw new JsonException();
            }
            reader.Read();
        }

        return result;
    }

    public override void Write(Utf8JsonWriter writer, (T1, T2) value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WritePropertyName("Item1");
        JsonSerializer.Serialize<T1>(writer, value.Item1, options);
        writer.WritePropertyName("Item2");
        JsonSerializer.Serialize<T2>(writer, value.Item2, options);
        writer.WriteEndObject();
    }
}

public class ValueTupleConverter<T1, T2, T3> : JsonConverter<ValueTuple<T1, T2, T3>>
{
    public override (T1, T2, T3) Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        (T1, T2, T3) result = default;

        if (!reader.Read())
        {
            throw new JsonException();
        }

        while (reader.TokenType != JsonTokenType.EndObject)
        {
            if (reader.ValueTextEquals("Item1") && reader.Read())
            {
                result.Item1 = JsonSerializer.Deserialize<T1>(ref reader, options);
            }
            else if (reader.ValueTextEquals("Item2") && reader.Read())
            {
                result.Item2 = JsonSerializer.Deserialize<T2>(ref reader, options);
            }
            else if (reader.ValueTextEquals("Item3") && reader.Read())
            {
                result.Item3 = JsonSerializer.Deserialize<T3>(ref reader, options);
            }
            else
            {
                throw new JsonException();
            }
            reader.Read();
        }

        return result;
    }

    public override void Write(Utf8JsonWriter writer, (T1, T2, T3) value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WritePropertyName("Item1");
        JsonSerializer.Serialize<T1>(writer, value.Item1, options);
        writer.WritePropertyName("Item2");
        JsonSerializer.Serialize<T2>(writer, value.Item2, options);
        writer.WritePropertyName("Item3");
        JsonSerializer.Serialize<T3>(writer, value.Item3, options);
        writer.WriteEndObject();
    }
}

public class WeatherForecastValueTuple
{
    public ValueTuple<string> Temp { get; set; }
    public (int x, int y) Location { get; set; }
    public (int a, Poco b, ValueTuple<string> c) Thruple { get; set; }
}

public class Poco
{
    public int Alpha { get; set; }
    public (List<int>, string) Beta { get; set; }
}

private static void SupportValueTuple()
{
    var objectWithValueTuple = new WeatherForecastValueTuple
    {
        Location = (3, 7),
        Temp = new ValueTuple<string>("FOO"),
        Thruple = (
            8,
            new Poco
            {
                Alpha = 9,
                Beta = (new List<int>() { -1, 0, -1 }, "a list")
            },
            new ValueTuple<string>("Bar"))
    };

    var options = new JsonSerializerOptions();
    options.Converters.Add(new ValueTupleFactory());

    string jsonString = JsonSerializer.Serialize(objectWithValueTuple, options);
    Console.WriteLine(jsonString);

    WeatherForecastValueTuple roundTrip = JsonSerializer.Deserialize<WeatherForecastValueTuple>(jsonString, options);
    Assert.Equal((3, 7), roundTrip.Location);
    Assert.Equal(new ValueTuple<string>("FOO"), roundTrip.Temp);
}

Within Aspnet you can register it as follows:

services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.Converters.Add(new ValueTupleFactory());
});

@ahsonkhan ahsonkhan added area-System.Text.Json and removed untriaged New issue has not been triaged by the area owner labels Feb 8, 2020
@ahsonkhan ahsonkhan added this to the 5.0 milestone Feb 8, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 11, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants