System.Text.Json: How to serialise an IEnumerable like a regular class?
Asked Answered
M

1

8

It seems that if an object is an IEnumerable, Json ignores its fields and serialise the enumerable. For example, for a class like below, Title is not serialised. I have found almost the same question, and the answer was adding [JsonObject] to the class, but it was about using Newtonsoft.Json, and I am using .NET 7.0's Json via JsonResult. Is there something equivalent for .NET Json?

        var myBook = new Book()
        {
            Title = "Jane Eyre",
            Pages = new List<string>() { "page1", "page2", "page3" }
        };
        var options = new JsonSerializerOptions { IncludeFields = true };
        return new JsonResult(myBook, options);
    }

    public class Book:IEnumerable<string>
    {
        public string Title;
        public IList<string> Pages;

        public IEnumerator<string> GetEnumerator()
        {
            return Pages.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return Pages.GetEnumerator() as IEnumerator;
        }
    }
Mcavoy answered 29/11, 2022 at 13:55 Comment(0)
E
2

Not actually, but this has been requested :

Provide option for the serializer to treat IEnumerables like objects with members #1808

System.Text.Json has the converter JsonObjectConverter that can do the job... but it's internal.

In a future .NET release, internal converter will be exposed :

Developers should have access to System.Text.Json's default internal converters #63791

After, then you will can :

[JsonConverter(typeof(JsonObjectConverter))]
public class Book:IEnumerable<string>
{...}

For now, the workaround is to code a converter for each IEnumerable type like :

public class BookConverter : Json.Serialization.JsonConverter<Book>
{
    public override Book? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartObject)
        {
            throw new JsonException();
        }

        var obj = new Book();
        do
        {
            reader.Read();

            if (reader.TokenType == JsonTokenType.PropertyName)
            {
                string propName = reader.GetString();
                reader.Read();

                switch(propName)
                {
                    case "Title":
                        obj.Title = reader.GetString();
                        break;
                    case "Pages":
                        obj.Pages = JsonSerializer.Deserialize<IList<string>>(ref reader, options);
                        break;
                    default:
                        throw new JsonException();
                }
            }
            else if (reader.TokenType == JsonTokenType.EndObject)
            {
                break;
            }
        } while (true);

        return obj;
    }

    public override void Write(Utf8JsonWriter writer, Book value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WriteString("Title", value.Title);
        writer.WritePropertyName("Pages");
        JsonSerializer.Serialize(writer, value.Pages, options);
        writer.WriteEndObject();
    }
}
Eleanore answered 29/11, 2022 at 15:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.