14

Wondering if possible to have private constructors and use the new System.Text.Json serializer.

public class MyModel
{
    public string Name { get; set; }
    public string Data { get; set; }

    private MyModel() 
    {
        // use me for when deserializing
    }

    public MyModel(string name, string data)
    {
        Name = name;
        Data = data;
    }
}

A simple round trip.

var model = new MyModel("doo", "doo");
var json = JsonSerializer.Serialize(model, new JsonSerializerOptions
{
    WriteIndented = true
});

// no to go because of there is no parameterless constructor defined for this object.
var rehydrated = JsonSerializer.Deserialize<MyModel>(json);
jazb
  • 5,498
  • 6
  • 37
  • 44
smiggleworth
  • 534
  • 4
  • 16
  • 1
    There is breaking change between .net core 3.1 and .net core 5.0. Please see the following - https://learn.microsoft.com/en-us/dotnet/core/compatibility/serialization/5.0/non-public-parameterless-constructors-not-used-for-deserialization. It also suggests implementing a JsonConverter if a public constructor cannot be added. – Gary Chan Jan 12 '21 at 19:46

3 Answers3

15

Update in .NET 7.0

From .NET 7.0, deserialization can be done with a private parameterless constructor by writing your own ContractResolver as follows:

public class PrivateConstructorContractResolver : DefaultJsonTypeInfoResolver
{
   public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
   {
       JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);

       if (jsonTypeInfo.Kind == JsonTypeInfoKind.Object && jsonTypeInfo.CreateObject is null)
       {
         if (jsonTypeInfo.Type.GetConstructors(BindingFlags.Public | BindingFlags.Instance).Length == 0)
         {
            // The type doesn't have public constructors
            jsonTypeInfo.CreateObject = () => 
                Activator.CreateInstance(jsonTypeInfo.Type, true);
         }
       }

      return jsonTypeInfo;
   }
}

Use as follows:

private static void Main(string[] args)
{
    JsonSerializerOptions options = new JsonSerializerOptions
    {
       TypeInfoResolver = new PrivateConstructorContractResolver()
    };

    Employee employee = Employee.Create(1, "Tanvir");
    string jsonString = JsonSerializer.Serialize(employee);
    Employee employee1 = JsonSerializer.Deserialize<Employee>(jsonString , options);
}

public class Employee
{
   private Employee()
   {
   }

   private Employee(int id, string name)
   {
     Id = id;
     Name = name;
   }

   [JsonInclude]
   public int Id { get; private set; }

   [JsonInclude]
   public string Name { get; private set; }

   public static Employee Create(int id, string name)
   {
     Employee employee = new Employee(id, name);
     return employee;
   }
}
TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
  • 1
    Thank you, I had missed this new feature in the RC. – DhyMik Oct 25 '22 at 14:24
  • 1
    I went with this clean solution, but I had to modify the ContractResolver so that it wasn't required to have no public constructors. I actually created a static HashSet that I could register Type.FullName with so the ContractResolver could selectively designate which classes I'd force JsonSerializer to use the private constructor. – Sean Nov 23 '22 at 19:23
  • 1
    You can also do this by adding a type resolver modifier, which lets you have many JsonTypeInfo resolvers instead of just 1. It only consists of your first `if` and everything under it – Douglas Gaskell Jun 21 '23 at 17:37
5

It would appear the answer is "No," or at least, "Not Yet".

This is a known limitation of the System.Text.Json serializer for [System.Text.Json] v1. We plan to support this in the future. -ashonkhan

You can write a custom converter for this...For the [ASP.NET Core] 3.0 release, there is no planned additional support for calling a non-default constructor during deserialization. That would have to be done by a custom converter. -steveharter

The custom converter option linked would allow you to use whatever API you do have to build the object, but isn't the same as what, say, Newtonsoft.Json or Entity Framework can do by fiddling with reflection and private constructors, so probably not what you were looking for.

S'pht'Kr
  • 2,809
  • 1
  • 24
  • 43
-1

you need to use a JsonConstructor attribute

check the link below

https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-immutability?pivots=dotnet-5-0

Felipe Augusto
  • 1,341
  • 1
  • 16
  • 18