平常在編碼中常常會遇到一些情況下需要重寫序列化與反序列化的方法. 例如一時間的格式等等. 下面我們用一個Graph結構來舉例.

  • Graph的節點
public interface INode
{
    int Type { get; set; }
    string Name { get; set; }
}
  • 假定我們的節點有很多種
public class BlueNode:INode
{
    public int Type { get; set; } = 1;
    public string Name { get; set; } = "Blue Node";
}
public class GreenNode:INode
{
    public int Type { get; set; } = 2;
    public string Name { get; set; } = "Green Node";
}
public class RedNode:INode
{
    public int Type { get; set; } = 3;
    public string Name { get; set; } = "Red Node";
}
  • Graph的結構(因爲是舉例序列化與反序列化,因此Graph中我們省掉了Edges)
public class Graph
{
    /// <summary>
    /// 圖的名稱
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 創建圖的時間
    /// </summary>
    public DateTime CreateTime { get; set; } = DateTime.Now;

    /// <summary>
    /// 圖中的點
    /// </summary>
    public IEnumerable<INode> Nodes;
}
  • 先測驗一下默認的序列化方式
static void Main(string[] args)
{
    var graph=new Graph()
    {
        Name = "Colorful Graph",
        Nodes = new INode[]
        {
            new BlueNode(), 
            new RedNode(), 
            new GreenNode(), 
        }
    };
    var fmt = new JsonSerializerSettings
    {
        ContractResolver = new CamelCasePropertyNamesContractResolver(),
        Formatting = Formatting.Indented,
    };
    Console.WriteLine(JsonConvert.SerializeObject(graph,fmt));
}

輸出:

{
  "nodes": [
    {
      "type": 1,
      "name": "Blue Node"
    },
    {
      "type": 2,
      "name": "Red Node"
    },
    {
      "type": 3,
      "name": "Green Node"
    }
  ],
  "name": "Colorful Graph",
  "createTime": "2020-08-17T10:35:38.7293642+08:00"
}

  • 自定義序列化時間 可以看出,輸出的序列化之後的json,CreateTime部分時間的格式看起來和我們平常使用的日期表達很不一樣,因此我們的第一個目標是對CreateTime時間序列進行客制化.

首先需要創建一個繼承自Newtonsoft.Json.JsonConverter的針對時間的序列化器

public class DateTimeSerializer:JsonConverter
{
    /// <summary>
    /// 序列化時會調用
    /// </summary>
    /// <param name="writer"></param>
    /// <param name="value"></param>
    /// <param name="serializer"></param>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteValue(((DateTime)value).ToString("yyyy-MM-dd"));
    }
    /// <summary>
    /// 反序列化時調用
    /// </summary>
    /// <param name="reader"></param>
    /// <param name="objectType"></param>
    /// <param name="existingValue"></param>
    /// <param name="serializer"></param>
    /// <returns></returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var dateString = (string) reader.Value;
        var date = DateTime.Parse(dateString);
        return date;
    }
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

然後修改Graph類

public class Graph
{
    /// <summary>
    /// 圖的名稱
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 創建圖的時間(這裏不一樣了)
    /// </summary>
    [JsonConverter(typeof(DateTimeSerializer))]
    public DateTime CreateTime { get; set; } = DateTime.Now;
    
    /// <summary>
    /// 圖中的點
    /// </summary>
    public IEnumerable<INode> Nodes;
}

再次執行序列化,打印出:

{
  "nodes": [
    {
      "type": 1,
      "name": "Blue Node"
    },
    {
      "type": 2,
      "name": "Red Node"
    },
    {
      "type": 3,
      "name": "Green Node"
    }
  ],
  "name": "Colorful Graph",
  "createTime": "2020-08-17"
}

結果已經發生了變化

  • 自訂反序列化Interface屬性 有時候在反序列化時,會遇到類似在本例中的Graph,其中Nodes是一個界面集合,此時序列化通常不會有什麽問題. 但儅我們用以下json來進行反序列化時:
static void Main(string[] args)
{
    var jsonStr = @"
        {
          'nodes': [
            {
              'type': 1,
              'name': 'Node 1'
            },
            {
              'type': 2,
              'name': 'Node 2'
            },
            {
              'type': 3,
              'name': 'Node 3'
            }
          ],
          'name': 'Colorful Graph',
          'createTime': '2020-08-17'
        }";
    var graph = JsonConvert.DeserializeObject<Graph>(jsonStr);
    Console.WriteLine(graph.Name);
}

結果是會發生異常

Newtonsoft.Json.JsonSerializationException: Could not create an instance of type ConsoleApp2.INode. Type is an interface or abstract class and cannot be instantiated. Path 'nodes[0].type', line 5, position 27......

原因是這裏嘗試將json反序列為Graph時,在創建nodes實例的時候,INode是個界面,儅JsonConvert在DeserializeObject的時候,就無法自動完成了,因爲不曉得json中nodes的元素應該被轉譯為BlueNode還是GreenNode或者RedNode,此時,就需要自訂反序列化的方式了.我們假定把"type”:1的全部反序列化成BludNode,“type”:2的全部反序列化成GreenNode,“type”:3的全部反序列化成RedNode

首先創建針對Nodes的序列化器

public class NodeSerializer : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
    {
        //這裏序列化采用默認行爲
    }

    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue,
        JsonSerializer serializer)
    {
        var jArr = JArray.Load(reader);
        var nodes = new List<INode>();
        foreach (var item in jArr)
        {
            var type = item.Value<int>("type");//此處通過type區分元素反序列化的行爲
            INode node;
            switch (type)
            {
                case 1:
                    node = item.ToObject<BlueNode>();
                    break;
                case 2:
                    node = item.ToObject<GreenNode>();
                    break;
                case 3:
                    node = item.ToObject<RedNode>();
                    break;
                default: continue;
            }
            nodes.Add(node);
        }
        return nodes;
    }
    
    /// <summary>
    /// 這裏必須設定CanWrite為false,否則必須實現WriteJson()
    /// </summary>
    public override bool CanWrite { get; }= false;
    public override bool CanConvert(Type objectType)
    {
        return true;
    }
}

然後修改Graph類

public class Graph
{
    /// <summary>
    /// 圖的名稱
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 創建圖的時間(這裏不一樣了)
    /// </summary>
    [JsonConverter(typeof(DateTimeSerializer))]
    public DateTime CreateTime { get; set; } = DateTime.Now;
    
    /// <summary>
    /// 圖中的點(這裏不一樣了)
    /// </summary>
    [JsonConverter(typeof(NodeSerializer))]
    public IEnumerable<INode> Nodes;
}

再運行測試程式碼

static void Main(string[] args)
{
    var jsonStr = @"
        {
          'nodes': [
            {
              'type': 1,
              'name': 'Blue Node'
            },
            {
              'type': 2,
              'name': 'Green Node'
            },
            {
              'type': 3,
              'name': 'Red Node'
            }
          ],
          'name': 'Colorful Graph',
          'createTime': '2020-08-17'
        }";
    var graph = JsonConvert.DeserializeObject<Graph>(jsonStr);
    Console.WriteLine("deserialize success.");
    Console.WriteLine(graph.Name);
}

打印出:

deserialize success.
Colorful Graph

PS:這只是諸多自訂序列化與反序列化的其中一種方式,能滿足諸多自定義的情況,其他更多資訊,請參考Newtonsoft.Json的文檔