KarlSupertramp
KarlSupertramp

Reputation: 85

C# - Serialize all attributes of derived class in a list of base class

I need to JsonSerializer.Serialize(...) a class containig a list of a base class:

// ----- Models -----


public class MainClass
{
    [Key]
    public Guid Id { get; private set; } = Guid.NewGuid();
    public List<BaseClass> Properties { get; set; } = new List<BaseClass>();
}
  
public class BaseClass
{
    [Key]
    public Guid Id { get; private set; } = Guid.NewGuid();
    public string Name { get; set; } = string.Empty;
}

public class GenericDerivedClass<T> : BaseClass
{
    public T? Value { get; set; }
}



// ----- Implementation -----



var main = new MainClass
{
    Properties = new List<BaseClass>
    {
       new GenericDerivedClass<string>
       {
           Name = "SoundFile",
           Value = "Test.wav"
       },
        new GenericDerivedClass<float>
        {
            Name = "Volume",
            Value = 1
        },
        new GenericDerivedClass<bool>
        {
            Name = "Autoplay",
            Value = false
        },
        new GenericDerivedClass<bool>
        {
            Name = "Loop",
            Value = false
        },
    }
};

Console.WriteLine(JsonSerializer.Serialize(main, new JsonSerializerOptions { WriteIndented = true }));

// ----- Output (JsonSerializer) ----

[
  {
    "main": [
      {
        "id": "ba348c86-aa86-45ea-8d21-a9beddd4368a",        
        "properties": [
          {
            "id": "a9f432d5-3916-4c1d-b44a-fd4b7d8fcb45",
            "name": "SoundFile",
            //"value": "Test.wav" <- I want this line here, but I cannot figure out how.
          },
          {
            "id": "f585d863-b0d7-49b3-ad5c-0565171e6793",
            "name": "Volume"
          },
          {
            "id": "197802f3-17cd-4c1f-90be-7ea643ee5d7d",
            "name": "Autoplay"
          },
          {
            "id": "b90e3857-e497-4137-adeb-94b66293d375",
            "name": "Loop"
          }
        ]
      }
    ],
  }
]

The problem here is, that only the properties of the base class (BaseClass) are serialized (Id and Name). Is there a way to serialize the class "MainClass" with a List<BaseClass> containing the information (Value) of each GenericDerivedClass<T>?

(Using List<object> instead of List<BaseClass> is not an option, since I cannot use primitive types.)

Upvotes: 3

Views: 1262

Answers (3)

ajbeaven
ajbeaven

Reputation: 9582

Beginning with .NET 7, System.Text.Json supports polymorphic type hierarchy serialization and deserialization with attribute annotations.

[JsonDerivedType(typeof(Dog))]
[JsonDerivedType(typeof(Cat))]
public class Animal
{
    // ...snip
}

I haven't tested this with a derived classes that have a generic constraint like in your example, but I imagine it would be something similar:

[JsonDerivedType(typeof(GenericDerivedClass<string>))]
[JsonDerivedType(typeof(GenericDerivedClass<float>))]
[JsonDerivedType(typeof(GenericDerivedClass<bool>))]
public class BaseClass
{
    // ...snip
}

Upvotes: 2

KarlSupertramp
KarlSupertramp

Reputation: 85

Olivers comment got me on the right track, thank you.

It's a bit tideous, to go over all "propertynames" in a switch, but it seems to do the trick!

If you run into the same problem, you have to write a custom converter, like in the c# example shown here:

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-6-0#support-polymorphic-deserialization

Here is my code (overriding "Write()" in a class deriving from JsonConverter<>):

public class PropertyConverter : JsonConverter<Property>
{
    public override bool CanConvert(Type typeToConvert) =>
        typeof(Property).IsAssignableFrom(typeToConvert);

    public override Property? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, Property value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WriteString("name", value.Name);

        if (value is GenericProperty<float> floatProperty)
        {
            writer.WriteNumber("value", floatProperty.Value);
        }
        else if (value is GenericProperty<int> intProperty)
        {
            writer.WriteNumber("value", intProperty.Value);
        }
        else if (value is GenericProperty<string> stringProperty)
        {
            writer.WriteString("value", stringProperty.Value);
        }
        else if (value is GenericProperty<bool> boolProperty)
        {
            writer.WriteBoolean("value", boolProperty.Value);
        }            

        writer.WriteEndObject();
    }

}

Make sure to add the custom converter to JsonSerializerOptions:

 var serializeOptions = new JsonSerializerOptions()
        {
            WriteIndented = true,
        };
        serializeOptions.Converters.Add(new PropertyConverter());

       Console.WriteLine(JsonSerializer.Serialize(_json, serializeOptions));

Upvotes: 2

Yonatan Rubin
Yonatan Rubin

Reputation: 61

System.Text.Json does not support it by design. However there might be a workaround- if you add [JsonIgnore] before the Properties property and add a property such as

[JsonPropertyName] public IEnumerable<object> JsonProperties {get=> Items;}

Keep in mind that you still can't deserialize it

Upvotes: 1

Related Questions