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

System.Text.Json should allow any JSON type to be able to deserialzie to string type. #106245

Closed
John0King opened this issue Aug 12, 2024 · 5 comments

Comments

@John0King
Copy link

user case

json from a API

//json-a

{
    "Id":"Abc123"
}

//json-b
{
    "Id": 123
}

the mode class

public class Foo
{
     public string Id { get;set;}
}

current System.Text.Json doesn't allow this deserialzation, and it throw on "jsonb" ,
to deserialze this , the model should change the type from string to "JsonNode/JsonElement" . but the "JsonNode/JsonElement" is for dynamic complex type, and for this json model , the best type for the property is string, which means the number version json is wrong (may be because the api is using a dynamic type language like javascript)

suggestion

JsonSeriazlieOptions.StringHanding = JsonStringHanding.AllowAnyType;

public enum JsonStringHanding
{
    Strict,
    AllowNumbersAndBollean,
    AllowAnyType
}

refence

@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Aug 12, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis
See info in area-owners.md if you want to be subscribed.

@eiriktsarpalis
Copy link
Member

Per #56760 and #37354 this is something that STJ doesn't permit by design. The suggested workaround is to author a string converter that handles the desired types: (number, boolean or perhaps even object and array).

@eiriktsarpalis eiriktsarpalis closed this as not planned Won't fix, can't repro, duplicate, stale Aug 12, 2024
@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label Aug 12, 2024
@elgonzo
Copy link

elgonzo commented Aug 12, 2024

For special cases like these use a JsonConverter. Something like this example converter, which reads any kind of json value as a string, except the json null which will be translated to a null value:

class ReadAsStringConverter : JsonConverter<string>
{
    public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => JsonElement.ParseValue(ref reader).ToString();

    public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
        => _defaultConverter.Write(writer, value, options);

    private static readonly JsonConverter<string> _defaultConverter = (JsonConverter<string>) JsonSerializerOptions.Default.GetConverter(typeof(string));
}

(Side note: Using the default converter like demonstrated here currently doesn't work with collection converters, until issue #50205 is resolved.)

@eiriktsarpalis
Copy link
Member

Or if you prefer to avoid the intermediate JsonElement allocation you can do a pattern match directly on the reader:

class ReadAsStringConverter : JsonConverter<string>
{
    public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return reader.TokenType switch
        {
            JsonTokenType.Null => null,
            JsonTokenType.String => reader.GetString(),
            JsonTokenType.False => "false",
            JsonTokenType.True => "true",
            JsonTokenType.Number => 
                reader.TryGetDouble(out double number)
                ? number.ToString()
                : JsonElement.ParseValue(ref reader).ToString(),

            _ => throw new JsonException(),
        };
    }

    public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
    {
        if (value is null)
        {
            writer.WriteNullValue();
        }
        else
        {
            writer.WriteStringValue(value);
        }
    }
}

@elgonzo
Copy link

elgonzo commented Aug 12, 2024

reader.TryGetDouble(out double number)
   ? number.ToString()
   : JsonElement.ParseValue(ref reader).ToString(),

That's giving inaccurate/incorrect results because of converting the json number into an inherent imprecise binary floating point type. As a result, large integers with very large absolute values (including such that would be otherwise readable as long/ulong) and some particular real numbers would not be correctly converted to strings. Neither would be trailing fractional zeros preserved (as would be possible when the deserialization target were a decimal).

The conversion can be made in a way to preserve the the original json number accurately (i guess):

    ...
    JsonTokenType.Number =>
        Encoding.UTF8.GetString(reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan),
    ...

@github-actions github-actions bot locked and limited conversation to collaborators Sep 28, 2024
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

3 participants