Runtime: System.Text.Json : How deserialize an array of [Flag] enum

Created on 22 Aug 2019  路  7Comments  路  Source: dotnet/runtime

Hi folks,

I'm trying to deseralize an array of values of an enum but I can not get array values in JsonConverter. These are my questions:

  1. How return a value containing value1 | values n ...
  2. Why is working converter if [JsonPropertyName("Device")] is commented?
  3. Related with https://github.com/dotnet/corefx/issues/40497 json content should use enum literals too

Cheers

{
  "Connections": [
    {
      "Device": [ 2, 8 ]
    },
    {
      "Device": [ 2, 4 ]
    }   
  ]
}

```c#
class Program
{
static void Main(string[] args)
{
string json = File.ReadAllText("connexions.json");
var entry = JsonSerializer.Deserialize(json);
}

    internal sealed class ConnectionList
    {
        [JsonPropertyName("Connections")]
        public List<ConnectionFile> Connections { get; set; }
    }

    public sealed class ConnectionFile
    {
        //[JsonPropertyName("Device")]
        public eDevice Device { get; set; }
    }

    [Flags]
    [JsonConverter(typeof(JsonToEnumDeviceConverter))]
    public enum eDevice : uint
    {
        Unknown = 0,
        Phone = 1,
        PC = 2,
        Laptop = 4,
        Tablet = 8,
        IoT = 16,
        Watch = 32,
        TV = 64,
        Hub = 128,
        Studio = 256,
        Book = 512,
        MediaCenter = 1024
    }

    internal sealed class JsonToEnumDeviceConverter : JsonConverter<eDevice>
    {
        public override eDevice Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            switch (reader.TokenType)
            {
                case JsonTokenType.StartArray:
                    /* Json.net equivalent (with string values)
                    JToken token = JToken.Load(reader);
                    List<string> items = token.ToObject<List<string>>();
                    */
                    break;
            }

            throw new NotImplementedException();
        }

        public override void Write(Utf8JsonWriter writer, eDevice value, JsonSerializerOptions options)
        {
            throw new NotImplementedException();
        }
    }
}

````

area-System.Text.Json

Most helpful comment

There is not a built-in converter to treat a flags enum as an array.

Changing the json from
@"{""Connections"": [{""Device"":[""PC"",""Tablet""]},{""Device"":[""PC"",""Laptop""]}]}"
to
@"{""Connections"": [{""Device"":""PC,Tablet""},{""Device"":""PC,Laptop""}]}"
works as expected.

I'll work on an example of a custom converter that uses an array.

All 7 comments

Isn't this supported with JsonStringEnumConverter? What happens when you use this built-in converter in the JsonConverterAttribute instead of your own?

@Joelius300

I'm using 4.6.0-preview8.19405.3.

According https://github.com/dotnet/corefx/blob/master/src/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs this class should be in System.Text.Json.Serialization but it is missing:

Sense t铆tol

Cheers

It's there, it just doesn't show because it isn't an attribute. You have to use that converter as the type in the JsonConverterAttribute.
Try this:

[JsonConverter(typeof(JsonStringEnumConverter))]

@Joelius300,

Test 1 : Failing
System.Text.Json.JsonException: 'The JSON value could not be converted to ConsoleCore.Program+eDevice. Path: $.Connections[0].Device | LineNumber: 3 | BytePositionInLine: 17.'

`
static void Main(string[] args)
{
string json = File.ReadAllText("connexions.json");
var entry = JsonSerializer.Deserialize(json);
}

    internal sealed class ConnectionList
    {
        [JsonPropertyName("Connections")]
        public List<ConnectionFile> Connections { get; set; }
    }

    public sealed class ConnectionFile
    {

        [JsonPropertyName("Device")]
        public eDevice Device { get; set; }
    }

    [Flags]
    [JsonConverter(typeof(JsonStringEnumConverter))]
    public enum eDevice : uint
    {
        Unknown = 0,
        Phone = 1,
        PC = 2,
        Laptop = 4,
        Tablet = 8,
        IoT = 16,
        Watch = 32,
        TV = 64,
        Hub = 128,
        Studio = 256,
        Book = 512,
        MediaCenter = 1024
    }
}

`

Test 2 : Failing
System.Text.Json.JsonException: 'The JSON value could not be converted to ConsoleCore.Program+eDevice. Path: $.Connections[0].Device | LineNumber: 3 | BytePositionInLine: 17.'

{ "Connections": [ { "Device": [ "PC", "Tablet" ] }, { "Device": [ "PC", "Laptop" ] } ] }
Json.net: using a converter I can return PC | Tablet | ... into Device property. This is the code I'm using;

`
using Newtonsoft.Json;

public class JsonFlagConverter : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
    {
        switch (reader.TokenType)
        {
            case JsonToken.StartArray:
                {
                    JToken token = JToken.Load(reader);
                    List<string> items = token.ToObject<List<string>>();
                    List<ulong> converted = new List<UInt64>();
                    Type t = objectType.GetEnumUnderlyingType();

                    if (t == typeof(Int16))
                        converted = FromInt16(items, objectType);
                    else if (t == typeof(Int32))
                        converted = FromInt32(items, objectType);
                    else if (t == typeof(Int64))
                        converted = FromInt64(items, objectType);
                    else if (t == typeof(Byte))
                        converted = FromByte(items, objectType);
                    else if (t == typeof(UInt16))
                        converted = FromUInt16(items, objectType);
                    else if (t == typeof(UInt32))
                        converted = FromUInt32(items, objectType);
                    else if (t == typeof(UInt64))
                        converted = FromUInt64(items, objectType);
                    else
                        throw new NotImplementedException("Undefined conversor type " + t.FullName);

                    UInt64 sum = 0;
                    foreach (ulong n in converted)
                    {
                        if (sum == 0)
                            sum = n;
                        else
                            sum = sum | n;
                    }

                    return Enum.ToObject(objectType, sum);
                }

            default:
                throw new NotImplementedException(reader.TokenType.ToString());
        }
    }

    private List<UInt64> FromByte(List<string> items, Type objectType)
    {
        List<byte> e_items = items.Select(x => Enum.Parse(objectType, x)).Cast<Byte>().ToList();
        return e_items.ConvertAll(i => (UInt64)i);
    }

    private List<UInt64> FromInt16(List<string> items, Type objectType)
    {
        List<short> e_items = items.Select(x => Enum.Parse(objectType, x)).Cast<Int16>().ToList();
        return e_items.ConvertAll(i => (UInt64)i);
    }

    private List<UInt64> FromInt32(List<string> items, Type objectType)
    {
        List<int> e_items = items.Select(x => Enum.Parse(objectType, x)).Cast<Int32>().ToList();
        return e_items.ConvertAll(i => (UInt64)i);
    }

    private List<UInt64> FromInt64(List<string> items, Type objectType)
    {
        List<long> e_items = items.Select(x => Enum.Parse(objectType, x)).Cast<Int64>().ToList();
        return e_items.ConvertAll(i => (UInt64)i);
    }

    private List<UInt64> FromUInt16(List<string> items, Type objectType)
    {
        List<ushort> e_items = items.Select(x => Enum.Parse(objectType, x)).Cast<UInt16>().ToList();
        return e_items.ConvertAll(i => (UInt64)i);
    }

    private List<UInt64> FromUInt32(List<string> items, Type objectType)
    {
        List<uint> e_items = items.Select(x => Enum.Parse(objectType, x)).Cast<UInt32>().ToList();
        return e_items.ConvertAll(i => (UInt64)i);
    }

    private List<UInt64> FromUInt64(List<string> items, Type objectType)
    {
        List<ulong> e_items = items.Select(x => Enum.Parse(objectType, x)).Cast<UInt64>().ToList();
        return e_items.ConvertAll(i => (UInt64)i);
    }

    public override void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer)
    {
        //https://stackoverflow.com/questions/43143175/c-sharp-json-net-render-flags-enum-as-string-array?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
        IEnumerable<string> flags = value.ToString()
                         .Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries)
                         .Select(f => $"\"{f}\"");

        writer.WriteRawValue($"[{string.Join(", ", flags)}]");
    }

    public override bool CanConvert(Type objectType)
    {
        return true;
    }
}

`

Looks to me like this sample is representing a flags enum as either an array of ints ("Device": [ 2, 8 ]) or array of enum string values ("Device": [ "PC", "Tablet" ]). @steveharter do we have a built in converter for that, or is a custom converter required? /cc @ahsonkhan

There is not a built-in converter to treat a flags enum as an array.

Changing the json from
@"{""Connections"": [{""Device"":[""PC"",""Tablet""]},{""Device"":[""PC"",""Laptop""]}]}"
to
@"{""Connections"": [{""Device"":""PC,Tablet""},{""Device"":""PC,Laptop""}]}"
works as expected.

I'll work on an example of a custom converter that uses an array.

Thanks a lot @steveharter. Your solutions works.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jzabroski picture jzabroski  路  3Comments

bencz picture bencz  路  3Comments

omariom picture omariom  路  3Comments

noahfalk picture noahfalk  路  3Comments

omajid picture omajid  路  3Comments