-1

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; set; } = 0;
  public bool IsRequired { get; set; } = 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.

Nolan Blew
  • 374
  • 3
  • 18
  • 2
    I think you have to write a `JsonConverter` for your specific class `A`. If you have nested classes or some kind of collection, you don't care for these at first. Later write the same specific converter for the nested types if needed and [call them recursively](https://stackoverflow.com/a/36116462/1838048). If this doesn't help, be more specific about your written converter and the expected behaviour. – Oliver Aug 02 '22 at 06:22
  • 2
    "as JSON.net is not as extensible as Newtonsoft" - Newtonsoft.Json *is* Json.NET. Did you mean "as System.Text.Json is not as extensible as Newtonsoft.Json"? – Jon Skeet Aug 02 '22 at 07:26
  • Ahh yes sorry John that is what I meant! – Nolan Blew Aug 02 '22 at 21:20

2 Answers2

2

You just can add a json constructor to your class. You don't need to put all properties in the constructor, only those properties that behavier the special way

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

test

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

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

result

[
  {
    "Id": 4,
    "IsRequired": true
  },
  {
    "Id": 7,
    "IsRequired": false
  }
]
Serge
  • 40,935
  • 4
  • 18
  • 45
  • This does work! Though some of the classes I am dealing with have 30+ properties, and I have 20+ classes. Was looking to have something a little cleaner and less code/boiler plate for me to write. – Nolan Blew Aug 02 '22 at 21:23
  • @NolanBlew As you can see in my code you don't need to put all properties in the constructor, only those properties that behavier the special way or don't have set – Serge Aug 02 '22 at 21:38
  • Ah the code has two properties and you've explicitly set them both in your constructor, so I wasn't sure. I simplified the constructor a bit: ` [JsonConstructor] public A(bool? isRequired) { IsRequired= isRequired ?? false; } ` This is working for now. If you clean up your sample code a bit I'll mark it as an answer unless there's a better way of doing this! – Nolan Blew Aug 02 '22 at 23:51
  • @NolanBlew I added 2 properties to constructor because Id doesn't have a setter. You can not deserialize without a setter, unless you use a constructor. Fix your your question is Id can have get; and set; – Serge Aug 03 '22 at 00:00
  • Ah sorry Serge, in my real-life example all but one property have a setter. Fixed the question to include setters as well. And for cleaning up I was just talking about the top half of the code in the `//test` section - I think there's a orphaned `}` on the second line – Nolan Blew Aug 03 '22 at 21:17
0

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.

Maximilian Ast
  • 3,369
  • 12
  • 36
  • 47
  • It's not so much that I can't extend `A`, but rather I need the deserialized class to be `A` in order to use it elsewhere. I'd rather not write long converters (my classes have 30+ properties and I have 20+ classes), and I'd rather not use something like AutoMapper if I can avoid it. As mentioned in my OP, I've tried writing my own `JsonConverter` but I couldn't find a good base JsonConverter that worked (the samples on Newtonsoft github's were simplistic and only implemented write) – Nolan Blew Aug 02 '22 at 21:37