jeffreyk
jeffreyk

Reputation: 133

How to serialize all nulls to empty strings using Json.Net

I would like to serialize all nulls from string and nullable types to empty strings. I looked into using a custom JsonConverter but properties with nulls are not passed into the converter. I looked into using a contract resolver and value provider but it won't let me set the value of an int? to an empty string. What seems most promising is using a custom JsonWriter that overrides the WriteNull method, but when I instantiate that in the converter's WriteJson method, it doesn't write anything out.

public class NullJsonWriter : JsonTextWriter
{
    public NullJsonWriter(TextWriter writer) : base(writer)
    {
    }
    public override void WriteNull()
    {
        base.WriteValue(string.Empty);
    }
}

public class GamesJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(IEnumerable<IGame>).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer = new NullJsonWriter(new StringWriter(new StringBuilder()));
        writer.WriteStartObject();
        if (typeof (IEnumerable).IsAssignableFrom(value.GetType()))
        {
            var list = (IEnumerable<IGame>)value;
            if (!list.Any())
            {
                writer.WriteEndObject();
                return;
            }
            var properties = list.First().GetType().GetProperties();
            PropertyInfo gameId = null;
            foreach (var prop in properties)
            {
                if (prop.Name == "GameId")
                {
                    gameId = prop;
                    break;
                }
            }
            for (int i = 0; i < list.Count(); i++)
            {
                writer.WritePropertyName(string.Format("{0}", gameId.GetValue(list.ElementAt(i))));
                serializer.Serialize(writer, list.ElementAt(i));
            }
        }

        writer.WriteEndObject();
    }

I can see JSON text in the StringBuilder of the writer but it doesn't actually get written out to my webpage.

Upvotes: 1

Views: 8852

Answers (2)

kentor
kentor

Reputation: 18504

 JsonSerializer _jsonWriter = new JsonSerializer
            {
                NullValueHandling = NullValueHandling.Include
            };

Use this to make the serializer not ignore null values, then you should be able to override the serialization with the code you already have :)

Upvotes: 0

Brian Rogers
Brian Rogers

Reputation: 129667

I'm not sure I understand the reasoning of why you want to write out an empty string in place of all nulls, even those that might represent a Nullable<int>, or other object. The problem with that strategy is it makes the JSON more difficult to deal with during deserialization: if the consumer is expecting a Nullable<int> and they get a string instead, it leads to errors, confusion, and essentially forces the consuming code to use a special converter or weakly typed objects (e.g. JObject, dynamic) to get around the mismatch. But let's put that aside for now and say that you have your reasons.

You can definitely do what you want by subclassing the JsonTextWriter class and overriding the WriteNull method to write an empty string instead. The trick is that you must instantiate your own JsonSerializer instead of using JsonConvert.SerializeObject, since the latter does not have any overloads that accept a JsonWriter. Here is a short proof-of-concept that shows that this idea will work:

class Program
{
    static void Main(string[] args)
    {
        // Set up a dummy object with some data. The object also has a bunch
        // of properties that are never set, so they have null values by default.

        Foo foo = new Foo
        {
            String = "test",
            Int = 123,
            Decimal = 3.14M,
            Object = new Bar { Name = "obj1" },
            Array = new Bar[]
            {
                new Bar { Name = "obj2" }
            }
        };

        // The serializer writes to the custom NullJsonWriter, which
        // writes to the StringWriter, which writes to the StringBuilder.
        // We could also use a StreamWriter in place of the StringWriter
        // if we wanted to write to a file or web response or some other stream.

        StringBuilder sb = new StringBuilder();
        using (StringWriter sw = new StringWriter(sb))
        using (NullJsonWriter njw = new NullJsonWriter(sw))
        {
            JsonSerializer ser = new JsonSerializer();
            ser.Formatting = Formatting.Indented;
            ser.Serialize(njw, foo);
        }

        // Get the JSON result from the StringBuilder and write it to the console.
        Console.WriteLine(sb.ToString());
    }
}

class Foo
{
    public string String { get; set; }
    public string NullString { get; set; }
    public int? Int { get; set; }
    public int? NullInt { get; set; }
    public decimal? Decimal { get; set; }
    public decimal? NullDecimal { get; set; }
    public Bar Object { get; set; }
    public Bar NullObject { get; set; }
    public Bar[] Array { get; set; }
    public Bar[] NullArray { get; set; }
}

class Bar
{
    public string Name { get; set; }
}

public class NullJsonWriter : JsonTextWriter
{
    public NullJsonWriter(TextWriter writer) : base(writer)
    {
    }

    public override void WriteNull()
    {
        base.WriteValue(string.Empty);
    }
}

Here is the output:

{
  "String": "test",
  "NullString": "",
  "Int": 123,
  "NullInt": "",
  "Decimal": 3.14,
  "NullDecimal": "",
  "Object": {
    "Name": "obj1"
  },
  "NullObject": "",
  "Array": [
    {
      "Name": "obj2"
    }
  ],
  "NullArray": ""
}

Fiddle: https://dotnetfiddle.net/QpfG8D

Now let's talk about why this approach did not work as expected inside your converter. When JSON.Net calls into your converter, it already has a JsonWriter instantiated, which gets passed into the writer parameter of the WriteJson method. Instead of writing to that instance, what you have done is overwritten this variable with a new NullJsonWriter instance. But remember, replacing the contents of a reference variable does not replace the original instance that variable refers to. You are successfully writing to your new writer instance in the converter, but all the output that you are writing is going into the StringBuilder which you instantiated at the beginning of the WriteJson method. Since you never take the JSON result from the StringBuilder and do something with it, it is simply thrown away at the end of the method. Meanwhile, Json.Net continues to use its original JsonWriter instance, to which you have not written anything inside your converter. So, that is why you are not seeing your customized JSON in the final output.

There are a couple of ways to fix your code. One approach is just to use a new variable when instantiating the NullJsonWriter in your converter instead of hijacking the writer variable. Then, at the end of the WriteJson method, get the JSON from the StringBuilder and write it to the original writer using WriteRawValue method on the original writer.

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    StringBuilder sb = new StringBuilder();
    using (var innerWriter = new NullJsonWriter(new StringWriter(sb))
    {
        innerWriter.WriteStartObject();

        // ... (snip) ...

        innerWriter.WriteEndObject();
    }

    writer.WriteRawValue(sb.ToString());
}

Another approach, depending on your intended result, is not to use a separate writer inside your converter at all, but instead to create the NullJsonWriter instance at the very beginning of serialization and pass it to the outer serializer (as I did in the proof-of-concept earlier in the post). When Json.Net calls into your converter, the writer that is passed to WriteJson will be your custom NullJsonWriter, so at that point you can write to it naturally without having to jump through hoops. Keep in mind, however, that with this approach your entire JSON will have the nulls replaced with empty strings, not just the IEnumerable<IGame> that your converter handles. If you are trying to confine the special behavior to just your converter, then this approach will not work for you. It really depends on what you are trying to accomplish-- do you want the nulls replaced everywhere or just in part of the JSON? I did not get a good sense of this from your question.

Anyway, hope this helps.

Upvotes: 4

Related Questions