Shadowfax
Shadowfax

Reputation: 157

C# NewtonSoft Single Object or Array JsonConverter not working, no errors

I am trying to deserialize some JSON that is sometimes an array and sometimes an object. It must be generalized due to project constraints. I looked at this Stack Overflow question (Deserializing JSON when sometimes array and sometimes object) but the code doesn't work for my unit test. It doesn't error either, and the debugger shows that inside the converter it's returning objects like it should.

When I say it doesn't work, it deserializes everything until it gets to the JSON element in question, then it just serializes into an empty array like this:

"a": []

Here is my test JSON:

{
    "somefile": [
        {
            "name": "Bob",
            "a": {
                "asub": "Bob",
                "atracking": "Bob",
                "adate": " Bob"
            }
        },
        {
            "name": "Bob",
            "a": [
                {
                    "asub": "Bob",
                    "atracking": "Bob",
                    "adate": "Bob"
                },
                {
                    "asub": "Bob",
                    "atracking": "Bob",
                    "adate": "Bob"
                }
            ]
        }
    ]
}

Here is my C# Data Model with converter at the bottom:

public class DummyDataModel
{
    public List<SomeFile> someFile { get; } = new List<SomeFile>();
    public partial class SomeFile
    {
        public string name { get; set; } = "";

        [JsonConverter(typeof(SingleObjectOrArrayJsonConverter<SomeA>))]
        public List<SomeA> a { get; } = new List<SomeA>();
        //public SomeA a { get; set; } = new SomeA();
    }
    public partial class SomeA
    {
        public string asub { get; set; } = "";
        public string atracking { get; set; } = "";
        public string adate { get; set; } = "";
    }


    internal class SingleObjectOrArrayJsonConverter<T> : JsonConverter<List<T>> where T : class, new()
    {
        public override void WriteJson(JsonWriter writer, List<T> value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value.Count == 1 ? (object)value.Single() : value);
        }

        public override List<T> ReadJson(JsonReader reader, Type objectType, List<T> existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            return reader.TokenType switch
            {
                JsonToken.StartObject => new List<T> { serializer.Deserialize<T>(reader) },
                JsonToken.StartArray => serializer.Deserialize<List<T>>(reader),
                _ => throw new ArgumentOutOfRangeException($"Converter does not support JSON token type {reader.TokenType}.")
            };
        }
    }
}

Any tips on why it would go through each step without errors and still return nothing?

Here is the line for running this test (I am using WPF and C# 8.0)

DummyDataModel uTest = JsonConvert.DeserializeObject<DummyDataModel>(unitJSON);

txtResult.Text = $"Did Test:\n{JsonConvert.SerializeObject(uTest, Formatting.Indented)}";

Upvotes: 2

Views: 1764

Answers (1)

dbc
dbc

Reputation: 116670

Your a property is get-only, so in your ReadJson() method you need to populate the incoming List<T> existingValue list (which will be the current property value) rather than creating a new list:

internal class SingleObjectOrArrayJsonConverter<T> : JsonConverter<List<T>> where T : class, new()
{
    public override void WriteJson(JsonWriter writer, List<T> value, JsonSerializer serializer) =>
        // avoid possibility of infinite recursion by wrapping the List<T> with AsReadOnly()
        serializer.Serialize(writer, value.Count == 1 ? (object)value.Single() : value.AsReadOnly());

    public override List<T> ReadJson(JsonReader reader, Type objectType, List<T> existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        existingValue ??= new ();
        switch (reader.TokenType)
        {
            case JsonToken.StartObject: existingValue.Add(serializer.Deserialize<T>(reader)); break;
            case JsonToken.StartArray: serializer.Populate(reader, existingValue); break;
            default: throw new ArgumentOutOfRangeException($"Converter does not support JSON token type {reader.TokenType}.");
        };
        return existingValue;
    }
}

See also as this answer to How to handle both a single item and an array for the same property using JSON.net which shows a similar but more general converter.

Demo fiddle here.

Upvotes: 2

Related Questions