2

I want to create a JsonSerializable class, that I can inherit from. See this code:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;   

public interface IJson
{
    string ToJson();
}

abstract public class JsonSerializable : IJson
{
    public string ToJson()
    {
       return JsonSerializer.Serialize(this);
    }
}

public class Cabinet :JsonSerializable {
    [JsonPropertyName("name")]
    public string Name { get; set; }        
}

I use it as follows:

public class Program
{
    public static void Main()
    {
        var C = new Cabinet();
        C.Name = "FOO";

        IJson json_thing = C;
        
        Console.WriteLine(JsonSerializer.Serialize(C));
        Console.WriteLine(json_thing.ToJson());
    }
}

However, the result is:

{"name":"FOO"}
{}

Where I'd think json_thing.ToJson() should also result in {"name":"FOO"}.

Why is the JSON serialization with that extra in between class not working? I want that class in between, because the serialization code for most class will be identical.

2
  • JsonSerializer.Serialize<Cabinet>(...) vs JsonSerializer.Serialize<JsonSerializable>(...) Commented May 12 at 14:21
  • Try class JsonSerializable<T> : IJson where T: JsonSerializable<T> and class Cabinet : JsonSerializable<Cabinet> so you can do JsonSerializer.Serialize((T)this). Commented May 12 at 14:25

3 Answers 3

6

You need to take the JsonSerializer.Serialize overload that takes in the type. Otherwise you are using the generic version where the compile time type is inferred, i.e. JsonSerializable and not the derived classes:

abstract public class JsonSerializable : IJson {
    public string ToJson() {
        return JsonSerializer.Serialize(this, this.GetType());
    }
}
Sign up to request clarification or add additional context in comments.

Comments

5

Because of the difference in the declared type of the argument,

  • The working version calls: JsonSerializer.Serialize<Cabinet>(...)
  • The non-working version calls: JsonSerializer.Serialize<JsonSerializable>(...)

You can resolve the issue by replacing

return JsonSerializer.Serialize(this);

with

return JsonSerializer.Serialize(this, this.GetType());

Comments

0

In addition to already provided answers which explain why this happens and arguably the best option in this particular case, there are some more options you can consider which can be more convenient in other cases.

The first option to consider is to use the System.Text.Json's ability to handle type hierarchies - see the How to serialize properties of derived classes with System.Text.Json docs. For example via JsonDerivedTypeAttribute:

[JsonDerivedType(typeof(Cabinet))]
abstract public class JsonSerializable : IJson
{
    public string ToJson()
    {
       return JsonSerializer.Serialize(this);
    }
}

Demo @sharplab.io

This will require manually adding all derived types to the base (which can be kind of automated - see this answer) so again - the other answers should be considered first in the provided case.

Another option would be to consider the Curiously Recurring Template(Generic) Pattern:

abstract public class JsonSerializable<T> :  IJson where T : JsonSerializable<T>
{
    public string ToJson()
    {
       return JsonSerializer.Serialize(this as T);
    }
}

public class Cabinet : JsonSerializable<Cabinet> 
{
    [JsonPropertyName("name")]
    public string Name { get; set; }        
}

Demo @sharplab.io

This is obviously to cumbersome for this particular case (and requires providing correct generic type parameters in the descendant classes), but there are niche cases when this also can be useful/convenient.

1 Comment

A third way is to use introspection to locate the correct JsonSerializer.Serialize<> for type this.GetType()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.