简体   繁体   中英

C# Newtonsoft: Override Constructor Default Value When Deserializing

I have some fairly complex JSON and need to use Newtonsoft to deserialize, as System.Text.Json is not as extensible as Newtonsoft.

My problem is as follows: I have a class, let's call "A", which I use a few places in my app including saving to a DB using EFCore and converting to a binary file. I populate the class using a JSON File, but due to requirements beyond my control I need to keep the default value setter in the class. However, IF the property does NOT exist in the Json we are deserializing, I'd like to use a custom default value.

Example Class:

public class A
{
  public int Id { get; } = 0;
  public bool IsRequired { get; } = true;
}

And if this is my Json:

[{
  "id": 4,
  "isRequired": true;
},
{
  "id": 7
}]

I'd like to override the isRequired default to false if the key is not in the json.

var list = JsonConvert.DeserializeObject<List<A>>( -- some settings --);
// list[0]
//  - Id = 4
//  - IsRequired = true
//
// list[1]
//  - Id = 6
//  - IsRequired = false

I've been able to get a basic version of this working as described in this SO post , but the deserialization is very simple and breaks immediately with nested properties and complex types + my custom contract resolver I have.

Note that I am using .NET 6, and my models are separated from my deserializers.

There are two options using a JsonConverter for that

  1. The easy way (if you may extend the existing object)
    Since you already have written half of the answer within your question "I'd like to override the isRequired default to false" then lets use the intended behavior for overwriting in combination to a custom JsonConverter

    First lets make the IsRequired property virtual

     public class A { public int Id { get; set;} = 0; public virtual bool IsRequired { get; set;} = true; }

    Afterwards we can inherit and overwrite the default behavior:

     private class ADersialize: A { public override bool IsRequired { get; set;} = false; }

    With those modifications we can use a custom converter which uses ADeserialze :

     public class AConverter: JsonConverter { public static readonly AConverter Instance = new AConverter(); public override bool CanConvert(Type objectType) { return objectType == typeof(A); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { return serializer.Deserialize<ADersialize>(reader); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value); } }

    And with those changes you can just deserialize it:

     var list = JsonConvert.DeserializeObject<List<A>>(x, AConverter.Instance);
  2. Option two is when you cannot modify A at all. Then you also need to write a custom JsonConverter and within ReadJson initialize A and set IsRequired = false and afterwards deserialze each field individually.

you just need to add a constructor to your class, since you have only get (read only properties)

var list = JsonConvert.DeserializeObject<List<A>>(json);

//test

json=JsonConvert.SerializeObject(list, Newtonsoft.Json.Formatting.Indented);
}

public class A
{
    public int Id { get; } = 0;
    public bool IsRequired { get; } = true;
   
    [JsonConstructor]
    public A(int Id, bool? isRequired) {
    
    this.Id=Id;
    this.IsRequired= isRequired==null ? false: (bool)isRequired;
        
    }
}

You don't need to put all properties in the constructor, only those properties that behavier the special way or don't have set

result

[
  {
    "Id": 4,
    "IsRequired": true
  },
  {
    "Id": 7,
    "IsRequired": false
  }
]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM