Hi, I'm Ray

C#: Swapping Newtonsoft Json.NET for Text.Json

Programming .NET C#

Posted on Sat 12th Mar 2022

 Share Tweet


I’ve used Newtonsoft’s Json.NET since as far back as I can remember. It is a fantastic package for working with objects and JSON. When .NET’s own Text.Json namespace was release in 2019, I -like many- largely ignored it. There just wasn’t a need to switch to the new package and many of our other packages had a dependency for Json.NET anyway.

In 2021, I was upgrading an old project to .NET 5 and felt that the time was right for the move to Text.Json.

I have no doubt that there are countless articles around, comparing the performance between the two packages. My goal was just to reduce the number of third-party package dependencies.

Is the serialised JSON produced the same? Is the code very different?

This is what I found.
 

Is the code very different?

In short, no.

Employee employee1 = new Employee();
string json;

// newtonsoft
json = Newtonsoft.Json.JsonConvert.SerializeObject(employee1);
employee1 = Newtonsoft.Json.JsonConvert.DeserializeObject<Employee>(json);

// text-json
json = System.Text.Json.JsonSerializer.Serialize(employee1);
employee1 = System.Text.Json.JsonSerializer.Deserialize<Employee>(json);

But also, yes, more about this later.
 

Is the JSON the same?

Yes, sort of but actually, no.

I serialised the same object with a number of common-typed properties. The results were identical with both packages.

public class Employee
{
  public Guid ID { get; set; } = Guid.NewGuid();
  public string Name { get; set; } = "Anders Anderson";
  public DateTime DateOfBirth { get; set; } = new DateTime(2000, 1, 1);
  public int TelExt { get; set; } = 101;
  public KeyValuePair<int, int> Seat1 { get; set; } = new KeyValuePair<int, int>(0, 0);
  public Tuple<int, int> Seat2 { get; set; } = new Tuple<int, int>(0, 0);
}

//{ID:e2064746-8dbf-4e1f-bba6-cb38c41fbdd7,Name:Anders Anderson,DateOfBirth:2000-01-01T00:00:00,TelExt:101,Seat1:{Key:0,Value:0},Seat2:{Item1:0,Item2:0}}

When I tested with some types from the System.Drawing namespace, the results were unfortunately different.

public class Employee
{
  public Point Office { get; set; } = new Point(0, 0);        
  public Size Workspace { get; set; } = new Size(2, 1);
}

// newtonsoft
//{"Office":"0, 0","Workspace":"2, 1"}

// text-json
//{"Office":{"IsEmpty":true,"X":0,"Y":0},"Workspace":{"IsEmpty":false,"Width":2,"Height":1}}

 

Using user-defined converters

To retain backwards-compatibility for JSON created by Json.NET, we can define converters to customise how objects are serialised (and deserialised).

public class JsonPointConverter : JsonConverter
{
  public override Point Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  {
    Point result = new Point(0, 0);
    if (reader.TokenType == JsonTokenType.String)
    {
      string[] array = reader.GetString()!.Split(',');
      if (array.Length != 2)
      {
        return result;
      }

      int result2 = 0;
      int result3 = 0;
      if (!int.TryParse(array[0].Trim(), out result2))
      {
        result2 = 0;
      }

      if (!int.TryParse(array[1].Trim(), out result3))
      {
        result3 = 0;
      }

      return new Point(result2, result3);
    }

    return result;
  }

  public override void Write(Utf8JsonWriter writer, Point value, JsonSerializerOptions options)
  {
    writer.WriteStringValue($"{value.X.ToString()}, {value.Y.ToString()}");
  }
}

public class JsonSizeConverter : JsonConverter
{
  public override Size Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  {
    Size result = new Size(0, 0);
    if (reader.TokenType == JsonTokenType.String)
    {
      string[] array = reader.GetString()!.Split(',');
      if (array.Length != 2)
      {
        return result;
      }

      int result2 = 0;
      int result3 = 0;
      if (!int.TryParse(array[0].Trim(), out result2))
      {
        result2 = 0;
      }

      if (!int.TryParse(array[1].Trim(), out result3))
      {
        result3 = 0;
      }

      return new Size(result2, result3);
    }

    return result;
  }

  public override void Write(Utf8JsonWriter writer, Size value, JsonSerializerOptions options)
  {
    writer.WriteStringValue($"{value.Width.ToString()}, {value.Height.ToString()}");
  }
}

The converters above simplify the Point and Size types in a way that I thought would be obvious.

The implementation would be like this.

Employee employee1 = new Employee();

var options = new JsonSerializerOptions();
options.Converters.Add(new JsonPointConverter());
options.Converters.Add(new JsonSizeConverter());

string json = System.Text.Json.JsonSerializer.Serialize(employee1, options);
employee1 = System.Text.Json.JsonSerializer.Deserialize<Employee>(json, options);

 

And one last thing.

I encountered an unexpected problem when I had special characters in the JSON. Json.NET would escape the characters using the traditional slash (“\”) but Text.Json would encode the characters as unicode. This shouldn’t be a problem except that not a single JSON editor I found works that way. I end up with the unicode characters being stripped out.

public class Employee
{
  public string Name { get; set; } = "Anders \"Andy\" Anderson";
}

// newtonsoft
//{"Name":"Anders \"Andy\" Anderson"}

// text-json
//{"Name":"Anders \u0022Andy\u0022 Anderson"}

Lucky, this is an easy fix in the options.

var options = new JsonSerializerOptions();
options.Converters.Add(new JsonPointConverter());
options.Converters.Add(new JsonSizeConverter());
options.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping;

string json = System.Text.Json.JsonSerializer.Serialize(employee1, options);
employee1 = System.Text.Json.JsonSerializer.Deserialize<Employee>(json, options);

 

Well, that’s that.

I hope someone finds this useful.