Search code examples
c#deserializationsystem.text.json

How to deserialize array with null values?


I need to deserialize this json:

[null]

to a List<JsonNode>

unfortunately instead, I get this exception:

System.Text.Json.JsonException: 'The JSON value could not be converted to System.Text.Json.Nodes.JsonNode. Path: $[0] | LineNumber: 0 | BytePositionInLine: 5.'

How to deserialize to a list with a single null value in it?

This code

var toJson = JsonSerializer.Serialize<List<JsonNode>>(new List<JsonNode>{ null });

Gives this json:

[null]

But this code

var fromJson = JsonSerializer.Deserialize<List<JsonNode>>("[null]");

Throws above


Solution

  • Update this is a bug in System.Text.Json which has been fixed in .NET 8 as of Preview 5. See:

    A failing .NET 7 demo fiddle was created here.

    As a workaround in earlier versions, you could create a custom converter for List<JsonNode> like the following:

    public class JsonNodeListConverter : JsonConverter<IEnumerable<JsonNode?>>
    {
        public override bool CanConvert(Type objectType) => 
            objectType == typeof(List<JsonNode>) || objectType == typeof(IReadOnlyList<JsonNode>) || objectType == typeof(IReadOnlyCollection<JsonNode>) || objectType == typeof(JsonNode [])  || objectType == typeof(IEnumerable<JsonNode>);
        public override void Write(Utf8JsonWriter writer, IEnumerable<JsonNode?> value, JsonSerializerOptions options)
        {
            writer.WriteStartArray();
            foreach (var node in value)
                JsonSerializer.Serialize(writer, node, options);
            writer.WriteEndArray();
        }
        
        public override IEnumerable<JsonNode?> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 
        {
            if (reader.TokenType != JsonTokenType.StartArray)
                throw new JsonException();
            var list = new List<JsonNode?>();
            while (reader.ReadAndAssert().TokenType != JsonTokenType.EndArray)
            {
                list.Add(JsonSerializer.Deserialize<JsonNode>(ref reader, options));
            }
            return typeToConvert.IsArray ? list.ToArray() : list;
        }
    }
    
    public static class JsonExtensions
    {
        public static ref Utf8JsonReader ReadAndAssert(ref this Utf8JsonReader reader) { if (!reader.Read()) { throw new JsonException(); } return ref reader; }
    }
    

    Then use as follows:

    var options = new JsonSerializerOptions
    {
        Converters = { new JsonNodeListConverter() },
    };
    JsonSerializer.Deserialize<List<JsonNode>>("""[null]""", options)
    

    And you will be able to deserialize lists and arrays of JsonNode that include null values.

    Demo fiddle #2 here.

    Alternatively, you could deserialize to an intermediate IEnumerable<object> with the option JsonSerializerOptions.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode and your non-null JSON array values will be deserialized as JsonNode subtypes:

    var options = new JsonSerializerOptions
    {
        UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode,
    };
    var list = JsonSerializer.Deserialize<IEnumerable<object>>("""[null, [null], 1, {}, "a"]""",
                                                               options)
         ?.Cast<JsonNode?>().ToList();
    

    Demo fiddle #3 here.