Customize Newtonsoft’s Json string converter to solve problems such as reference loops

If you need to save various strange types of data in a unified and universal text format, then json string conversion is currently the most used solution, and it is more convenient than xml. Newtonsoft is a very commonly used json package. Everyone who has used it says it is good. Open nuget and search for it and you can deploy it to the project immediately.

But sometimes it is inevitable to encounter unexpected problems. The most common one is the reference loop.

For example, I created a class called myclass. Most of its fields are regular double string ints, but it has a field called neighbor to record which of its neighbors are. Obviously neighbor is also of type myclass, and it will definitely be This happens: A’s neighbor is B, and B’s neighbor points back to A. In this case, the default converter JsonConvert.SerializeObject method will immediately report an error. Then you have to do it yourself and customize the json serialization and deserialization methods.

First, you need to declare a dedicated JsonConvertor for the class that you want to customize serialization and deserialization. For example, here we need to customize a myJsonConvertor for myclass. Like the following code, several things need to be done:

1. myJsonConvertor inherits the base class JsonConverter;

2. Add the declaration [JsonConverter (typeof(myJsonConvertor))] to myclass, requiring the use of customized myJsonConvertor to handle json conversion

//Inherit the base class JsonConverter
public class myJsonConvertor : JsonConverter
{
        public override bool CanConvert(Type objectType){ }

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

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer){ }

}

//Stated here, use myJsonConvertor instead of the default conversion method
[JsonConverter (typeof(myJsonConvertor))]
public class myclass

The JsonConverter base class requires the implementation of three methods: CanConvert, ReadJson and WriteJson.

CanConvert determines whether it can be used for json conversion of a certain class. Just determine whether it is the target type.

public override bool CanConvert(Type objectType)
{
    return objectType == typeof(ParaJsonConvertor);
}

WriteJson converts specific objects into json strings. It has three parameters. The first one is the JsonWriter used for output. The second object is the object to be converted. The third one is not used for the time being. First, cast the object into the target class myclass, and then use C# reflection to traverse the fields one by one. The Getfields method of the Type class is used here to traverse all fieldinfo of the myclass class. Specifically for each fieldinfo, Name is the field name, and GetValue() can get the field value. For regular fields, just use the default conversion method. For fields that require special processing, you need to write your own method for converting strings.

For example, Neighbor of myclass does not need to record all the information of the neighbor, but only needs to record the Name field of Neighbor. This can avoid unlimited looping when converting strings.

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var jObject = new JObject();
            var entity = value as myclass;
            var type = typeof(myclass);
            var fields = type.GetFields();
            foreach (var field in fields)
            {
                //Get field name
                var name = field.Name;
                var v = field.GetValue(entity);
                //Judge based on field name
                if(name == "Neighbour")
                {
                    //Convert the field type into a string and add it to jObject together with Name
                    //Only the Name field of Neighbor is recorded here
                    var neighbor = v as myclass
                    jObject.Add(name, neighbor.Name);
                }
                else
                {
                    //Other fields use the default conversion method
                    jObject.Add(name, JToken.FromObject(v));
                }
            }
            jObject.WriteTo(writer);
        }

ReadJson generates object types based on Json strings. It passes in four parameters. Since we are sure to only generate objects of the myclass class, we only need to use the first JsonReader here. Use the Read() method to traverse the contents of the Json string. After each Read, you can get the field name through the Value attribute. There are two strategies. The first is to decide how to process each field based on the field name. The second is to use reflection to find the type of the value based on the field name and decide how to process it based on the type. The code below uses the second method. Finally, use SetValue of Type type to assign a value to each field.

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            myclass ret = new myclass();
            var type = typeof(myclass);
            while (reader.Read())
            {
                if (reader.Value is null) break;
                var fieldname = reader.Value.ToString();
                var FieldInfo = type.GetField(fieldname);
                if (FieldInfo == null) continue;
                var typename = FieldInfo.FieldType.Name;
                switch(typename)
                {
                    case "myclass"://Read fields of type myclass
                        {
                            //Generate myclass based on string
                            var value = reader.ReadAsString();
                            myclass m = new myclass(value);
                            FieldInfo.SetValue(ret, m);
                        }
                        break;
                    case "Double"://double type field uses ReadAsDouble
                        {
                            var value = reader.ReadAsDouble();
                            FieldInfo.SetValue(ret, value);
                        }
                        break;
                    case "Int32"://int type field uses ReadAsInt32()
                        {
                            var value = reader.ReadAsInt32();
                            FieldInfo.SetValue(ret, value);
                        }
                        break;
                    case "String"://string type field uses ReadAsString
                        {
                            var value = reader.ReadAsString();
                            FieldInfo.SetValue(ret, value);
                        }
                        break;
                    case "Boolean"://bool type field uses ReadAsBoolean
                        {
                            var value = reader.ReadAsBoolean();
                            FieldInfo.SetValue(ret, value);
                        }
                        break;
                    default:
                        break;
                }
            }
            return ret;
        }

At this point, myclass’s customized Json converter is completed, and it can be used normally in the win10.net4.8 environment. But the code also leaves a little tail:

First, the ReadJson method is searched through the C# type name. “Boolean””Int32” these type names may not be written this way in other environments.

Second, JsonReader supports double, int, decimal, bool and string types, but float short cannot be directly supported and can only be forced to a similar type for processing.