0

I need to have the System.Text.Json.JsonSerializer be able to deserialize this nested Dictionary type structure, but I'm getting the following error:

Unable to cast object of type 'System.Text.Json.JsonElement' to type 'System.String'.

I have the following JSON

{
  "ACL": {
    "granted": {
      "Business": {
        "Delete": [
          "Customer1",
          "Customer2"
        ]
      },
      "Account": {
        "Create": [
          "Customer1",
          "Customer3"
        ]
      }
    }
  }
}

Going into the following model

using System.Collections.Specialized;
public record AclRecord 
{
    public Dictionary<string, Dictionary<string, Dictionary<string, StringCollection>>> ACL { get; set; }
}

What would a JsonConverter to deserialize this type of triple nested Dictionary type structure look like?

Charles
  • 127
  • 6
  • Use var acl=JsonConvert.DeserializeObject(json); and forget about system.text.json forever – Serge Aug 13 '22 at 19:57
  • @Serge I don't have that option. There is an underlying service using System.Text.Json, so I need a converter to make it work. – Charles Aug 13 '22 at 20:04
  • Then create a custom converter. You dont have any choice. System.Text.Json works only for a very simple cases – Serge Aug 13 '22 at 20:12

2 Answers2

1

If you're willing to use List<string> instead of StringCollection for your deserialization target, this will work out of the box:

public record AclRecord
{
    public Dictionary<string, Dictionary<string, Dictionary<string, List<string>>>> ACL { get; set; }
}

var obj = JsonSerializer.Deserialize<AclRecord>(json);

If you MUST use StringCollection, you will need to write a custom converter.

David L
  • 32,885
  • 8
  • 62
  • 93
0

Replacing StringCollection with List<string> as suggested in this answer by David L is the best solution to your problem, as StringCollection is an untyped collection from .Net 1.1 and typed collections should always be preferred over untyped collections.

That being said, if you must deserialize a StringCollection with System.Text.Json (e.g. because you are working with a legacy class you cannot rewrite), you may do so with the following simple JsonConverter<T>:

public class StringCollectionConverter : JsonConverter<StringCollection>
{
    public override StringCollection? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
        JsonSerializer.Deserialize<List<string>>(ref reader, options)?.Aggregate(new StringCollection(), (sc, s) => { sc.Add(s); return sc; });
    
    public override void Write(Utf8JsonWriter writer, StringCollection value, JsonSerializerOptions options) => 
        JsonSerializer.Serialize(writer, value.Cast<string>(), options);
}

If your StringCollection is large and you would like to avoid an intermediate List<String>, modify Read() as follows:

public override StringCollection? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    if (reader.TokenType != JsonTokenType.StartArray)
        throw new JsonException();
    var sc = new StringCollection();
    while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
        sc.Add(reader.GetString());
    return sc;
}

Then add the converter to JsonSerializerOptions and deserialize as follows:

var options = new JsonSerializerOptions 
{ 
    Converters = { new StringCollectionConverter(), /* Add other converters here */ },
    // Set other options below
    WriteIndented = true,
};
var model = JsonSerializer.Deserialize<AclRecord>(json, options);

To configure the options for Asp.NET Core 3.1 and later, see How to set json serializer settings in asp.net core 3?.

Demo fiddles here and here.

dbc
  • 104,963
  • 20
  • 228
  • 340