0

I'm having a peculiar problem with supporting IEnumerable along with JSON serialization. I dug around for a while here and online in general, and I see this issue isn't new. However, what I found was quite old and no longer works when I apply them to my app. I'm on .NET 7.0 and this is a simple Console application. Also, I'm doing this on a Mac (which is kinda cool).

Here's the code that I have (note that the IEnumerable implementation is commented out in class JsonNumberRanges. Also, please forgive me, I haven't coded in C# for about 13 years, so I'm just getting back into it and my knowledge is quite old):

using System.Collections;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace JsonSerializationProblem
{
    public class NumberRange
    {
        int lowerBound;
        int upperBound;

        public NumberRange()
        {
            lowerBound = 0;
            upperBound = 0;
        }

        public int LowerBound
        {
            get => lowerBound;
            set => lowerBound = value;
        }

        public int UpperBound
        {
            get => upperBound;
            set => upperBound = value;
        }
    }

    public class JsonNumberRanges /*: IEnumerable<NumberRange>*/
    {
        string fileHeader;
        List<NumberRange> numberRanges;

        public JsonNumberRanges()
        {
            fileHeader = "This is a sample Json file.";
        }

        public string FileHeader
        {
            get => fileHeader;
            set => fileHeader = value;
        }

        public List<NumberRange> NumberRanges
        {
            get => numberRanges;
            set => numberRanges = value;
        }

        public NumberRange this[int index]
        {
            get => numberRanges[index];
            set => Add(value);
        }

        internal void Add(NumberRange numberRange)
        {
            if (numberRanges == null)
            {
                numberRanges = new List<NumberRange>();
            }

            numberRanges.Add(numberRange);
        }

        internal int Count
        {
            get
            {
                if (numberRanges == null)
                {
                    return 0;
                }

                return numberRanges.Count;
            }
        }

        /*
        public IEnumerator<NumberRange> GetEnumerator()
        {
            for (int i = 0; i < numberRanges.Count; i++)
            {
                yield return numberRanges[i];
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            // Call the generic version of GetEnumerator().
            return GetEnumerator();
        }
        */
    }
}

Here's the main program:

using System;
using System.Text.Json;

namespace JsonSerializationProblem
{
    public class Program
    {
        static void Main()
        {
            JsonNumberRanges ranges = new JsonNumberRanges();

            NumberRange range = new NumberRange();
            range.LowerBound = 1;
            range.UpperBound = 20;
            ranges.Add(range);

            range = new NumberRange();
            range.LowerBound = 21;
            range.UpperBound = 40;
            ranges.Add(range);

            range = new NumberRange();
            range.LowerBound = 41;
            range.UpperBound = 60;
            ranges.Add(range);

            range = new NumberRange();
            range.LowerBound = 61;
            range.UpperBound = 80;
            ranges.Add(range);

            range = new NumberRange();
            range.LowerBound = 81;
            range.UpperBound = 100;
            ranges.Add(range);

            string fileName = "TestJsonFile.json";

            var options = new JsonSerializerOptions { WriteIndented = true };
            string jsonString = JsonSerializer.Serialize(ranges, options);
            Console.WriteLine(jsonString);

            // Write the data to the file.
            File.WriteAllText(fileName, jsonString);

            // Now, read back from file.
            jsonString = File.ReadAllText(fileName);

            JsonNumberRanges newRanges = JsonSerializer.Deserialize<JsonNumberRanges>(jsonString);
        }
    }
}

With the IEnumerable code commented out, the following file is both created and read back in with no problem:

{
  "FileHeader": "This is a sample Json file.",
  "NumberRanges": [
    {
      "LowerBound": 1,
      "UpperBound": 20
    },
    {
      "LowerBound": 21,
      "UpperBound": 40
    },
    {
      "LowerBound": 41,
      "UpperBound": 60
    },
    {
      "LowerBound": 61,
      "UpperBound": 80
    },
    {
      "LowerBound": 81,
      "UpperBound": 100
    }
  ]
}

However, if I uncomment the code that supports IEnumerable, this is the file that is created (notice the FileHeader portion is missing):

[
  {
    "LowerBound": 1,
    "UpperBound": 20
  },
  {
    "LowerBound": 21,
    "UpperBound": 40
  },
  {
    "LowerBound": 41,
    "UpperBound": 60
  },
  {
    "LowerBound": 61,
    "UpperBound": 80
  },
  {
    "LowerBound": 81,
    "UpperBound": 100
  }
]

So when my code attempts to read the file back in, it crashes with the following call stack:

Unhandled exception. System.NotSupportedException: The collection type 'JsonSerializationProblem.JsonNumberRanges' is abstract, an interface, or is read only, and could not be instantiated and populated. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
 ---> System.NotSupportedException: The collection type 'JsonSerializationProblem.JsonNumberRanges' is abstract, an interface, or is read only, and could not be instantiated and populated.
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack& state, Utf8JsonReader& reader, NotSupportedException ex)
   at System.Text.Json.Serialization.Converters.IEnumerableOfTConverter`2.CreateCollection(Utf8JsonReader& reader, ReadStack& state, JsonSerializerOptions options)
   at System.Text.Json.Serialization.JsonCollectionConverter`2.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, TCollection& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo jsonTypeInfo)
   at JsonSerializationProblem.Program.Main() in <PATH REMOVED>/JsonSerializationProblem/Program.cs:line 56
[createdump] Gathering state for process 33717 
[createdump] Crashing thread 8af20 signal 6 (0006)
[createdump] Writing crash report to file /var/folders/2r/cm4pgxks7xvd840fcbv30rqh0000gn/T/coredump.33717.crashreport.json
[createdump] Crash report successfully written
[createdump] Writing minidump with heap to file /var/folders/2r/cm4pgxks7xvd840fcbv30rqh0000gn/T/coredump.33717
[createdump] Written 493744296 bytes (120543 pages) to core file
[createdump] Target process is alive
[createdump] Dump successfully written in 2832ms
bash: line 1: 33717 Abort trap: 6           "/usr/local/share/dotnet/dotnet" "<PATH REMOVED>/JsonSerializationProblem/bin/Debug/net7.0/JsonSerializationProblem.dll"

Like I mentioned above, I read a number of threads on Stack Overflow and online and attempted a number of recommended solutions. For example, I tried applying the [JsonObject] which didn't work (one thread suggested this). I looked at the JsonObjectAttribute class because a couple of thread pointed to that. I also found a thread on Reddit where someone was ranting about Microsoft's implementation of JSON support, which wasn't helpful. So far, nothing I've tried has worked.

Has anyone encountered this and were you able to solve it? If so, how did you do it?

Ken White
  • 123,280
  • 14
  • 225
  • 444
  • 2
    When you class is implementing `IEnumerable` it is serialized as collection (see the [Supported collection types in System.Text.Json](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/supported-collection-types?pivots=dotnet-7-0) doc). If you don't want that you will need to implement custom serializer for the class, as I understand. – Guru Stron May 11 '23 at 15:39
  • 3
    It's typically bad practice to implement IEnumerable on model classes like this. Is there a reason you need to have this class implement IEnumerable, rather than having consuming classes access its `NumberRanges` property? – StriplingWarrior May 11 '23 at 15:43
  • 1
    Is solution a (not surfacing as IEnumerable) a problem? To extend on first comment. If the properties of the IEnumerable implementing class would also serialize its properties you would have gotten Count and Capacity also for numberRanges (properties of List) and you couldn't serialize it anymore as Array. And normally that is what someone wants a List type as Array in Json. – Ralf May 11 '23 at 15:46
  • Agree that implementing IEnumerable on model classes isn't recommended, see [Why not inherit from List?](https://stackoverflow.com/q/21692193/3744182) for why. But if you **must** do it, and the result **must** be serialized as an JSON object, you will need to write a manual converter. See: [System.Text.Json: How to serialise an IEnumerable like a regular class?](https://stackoverflow.com/q/74615202), which looks to be a duplicate. Does that answer your question, or do you need help writing the converter? – dbc May 11 '23 at 15:48
  • Thank you everyone for your comments. I was arriving at the same conclusion...that I would need to build my own converter for the collection. Truth be told, I wanted to support IEnumerable so I could use foreach, but the trouble of supporting that isn't worth it when I can use an indexer and a for loop for the very same thing. Also, all your comments are helping me upgrade my thinking and programming to be more current...I'm so behind on C# and .NET. Thank you! – Kevin Feige May 11 '23 at 16:04
  • So I'm thinking about this more as I continue to read the references. Also, dopey me didn't look up the actual exception. I found more relevant stuff online by doing that. So is the suggestion to just make the list available directly and let the client work with it as it sees fit rather than "protecting" it with the wrapper? The wrapper has additional related information for the collection, but I'm starting to think I don't need to "protect" the list, at least in my case. – Kevin Feige May 11 '23 at 16:23
  • @KevinFeige - so where do you want to go with this question? Is it enough to close it as a duplicate of the above questions, or as "not reproducible", or do do you still need some help with something? – dbc May 11 '23 at 17:18
  • This can be closed out. I was just asking for a recommendation but it's not necessary to get it. Thanks. – Kevin Feige May 11 '23 at 22:42
  • This problem is also described in [System.Text.Json: How to serialise an IEnumerable like a regular class](https://stackoverflow.com/questions/74615202/system-text-json-how-to-serialise-an-ienumerable-like-a-regular-class)! – Kevin Feige May 11 '23 at 22:46

0 Answers0