Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get ReadJson to return "default" behavior - as if CanConvert returned false

  • I have created an implementation of JsonConverter
  • CanConvert always returns true.
  • In ReadJson I want to sometimes just use the "default" behavior, as if CanConvert had returned false and my ReadJson was never called.
  • Various other posts have suggested I do some variation of the following:

existingValue = existingValue ?? serializer
                                     .ContractResolver
                                     .ResolveContract(objectType)
                                     .DefaultCreator();
serializer.Populate(reader, existingValue);
  • However, this throws NullReferenceException on .DefaultCreator().
  • existingValue is always null
  • The ContractResolver that is returned from the serializer is my own. It extends json.net's built-in CamelCasePropertyNamesContractResolver and simply overrides the methods CreateConstructorParameters and CreatePropertyFromConstructorParameter

How do I tell json.net - "just kidding, I don't know how to create this thing, do whatever you would have done to create it had I told you that I couldn't create it"

Note that I've simplified the problem for discussion. I am anticipating someone will answer with "just have CanCreate return false" In fact, in a number of scenarios I can and should create the object.

like image 840
SFun28 Avatar asked Sep 14 '25 05:09

SFun28


1 Answers

From your question and comments, it sounds like you have some situations where you want a converter to read but not write, and others where you want it to write but not read. You've solved the problem by splitting the functionality into two converters and then having each converter's CanConvert method return true or false at the appropriate times. This is certainly a viable approach and seems to be working for you, which is great. However, I wanted to offer an alternative solution.

In addition to the CanConvert method, the base JsonConverter offers two virtual boolean properties which you can override: CanRead and CanWrite. (Both return true by default.) These properties directly control whether ReadJson and WriteJson are called by the serializer for a particular converter. So, for example, if CanRead returns false, then ReadJson will not be called and the default read behavior will be used instead, even though CanConvert returned true. This allows you to set up an asymmetric converter quite neatly. For example, you might have a situation where you want to deserialize a crazy JSON format into a more sane object structure, but when you serialize it again, you don't want to go back to the crazy JSON format-- you just want the default serialization. In that case you would override CanWrite in your converter to always return false. Then you could just leave the implementation of WriteJson blank or have it throw a NotImplementedException; it will never be called.

Your case sounds a little more complicated than that, but you still should be able to manipulate the CanRead and CanWrite properties to achieve your desired results. Below is a contrived example which shows how we can switch the ReadJson and WriteJson methods on and off depending on a situational variable.

public class Program
{
    public static void Main(string[] args)
    {
        string json = @"{""keys"":[""foo"",""fizz""],""values"":[""bar"",""bang""]}";

        CustomConverter converter = new CustomConverter();
        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.Converters.Add(converter);

        // Here we are reading a JSON object containing two arrays into a dictionary
        // (custom read) and then writing out the dictionary JSON (standard write)
        Console.WriteLine("--- Situation 1 (custom read, standard write) ---");
        converter.Behavior = ConverterBehavior.CustomReadStandardWrite;
        json = DeserializeThenSerialize(json, settings);

        // Here we are reading a simple JSON object into a dictionary (standard read)
        // and then writing out a new JSON object containing arrays (custom write)
        Console.WriteLine("--- Situation 2 (standard read, custom write) ---");
        converter.Behavior = ConverterBehavior.StandardReadCustomWrite;
        json = DeserializeThenSerialize(json, settings);
    }

    private static string DeserializeThenSerialize(string json, JsonSerializerSettings settings)
    {
        Console.WriteLine("Deserializing...");
        Console.WriteLine(json);
        var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json, settings);
        foreach (var kvp in dict)
        {
            Console.WriteLine(kvp.Key + ": " + kvp.Value);
        }

        Console.WriteLine("Serializing...");
        json = JsonConvert.SerializeObject(dict, settings);
        Console.WriteLine(json);
        Console.WriteLine();

        return json;
    }
}

enum ConverterBehavior { CustomReadStandardWrite, StandardReadCustomWrite }

class CustomConverter : JsonConverter
{
    public ConverterBehavior Behavior { get; set; }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IDictionary<string, string>).IsAssignableFrom(objectType);
    }

    public override bool CanRead
    {
        get { return Behavior == ConverterBehavior.CustomReadStandardWrite; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        Console.WriteLine("ReadJson was called");

        // Converts a JSON object containing a keys array and a values array
        // into a Dictionary<string, string>
        JObject jo = JObject.Load(reader);
        return jo["keys"].Zip(jo["values"], (k, v) => new JProperty((string)k, v))
                         .ToDictionary(jp => jp.Name, jp => (string)jp.Value);
    }

    public override bool CanWrite
    {
        get { return Behavior == ConverterBehavior.StandardReadCustomWrite; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Console.WriteLine("WriteJson was called");

        // Converts a dictionary to a JSON object containing 
        // a keys array and a values array from the dictionary
        var dict = (Dictionary<string, string>)value;
        JObject jo = new JObject(new JProperty("keys", new JArray(dict.Keys)),
                                 new JProperty("values", new JArray(dict.Values)));
        jo.WriteTo(writer);
    }
}

Output:

--- Situation 1 (custom read, standard write) ---
Deserializing...
{"keys":["foo","fizz"],"values":["bar","bang"]}
ReadJson was called
foo: bar
fizz: bang
Serializing...
{"foo":"bar","fizz":"bang"}

--- Situation 2 (standard read, custom write) ---
Deserializing...
{"foo":"bar","fizz":"bang"}
foo: bar
fizz: bang
Serializing...
WriteJson was called
{"keys":["foo","fizz"],"values":["bar","bang"]}

Fiddle: https://dotnetfiddle.net/BdtSoN

like image 106
Brian Rogers Avatar answered Sep 16 '25 20:09

Brian Rogers