1. 程式人生 > 實用技巧 >C#自定義Json序列化

C#自定義Json序列化

鑑於網上的此類文章講的不那麼好,特在此重新講一下

建立一個.Net Core控制檯程式,本文程式碼需要Nuget包Newtonsoft。安裝後就可以開始了
首先交代一下使用的類

public abstract class EntityBase
{
    public virtual long Id { get; set; }
}

public class Entity : EntityBase
{
    public override long Id { get; set; }
    public string Name { get; set; }
    [MyJsonIgnore]
    
public decimal Money { get; set; } public Category Category { get; set; } public List<Child> Children { get; set; } } public class Child : EntityBase { public string Name { get; set; } } public enum Category { Human = 0, Cat = 1, Dog = 2 }
作為模型的類

預設情況下的序列化

public class
Program { public static void Main() { var entity = new Entity { Id = 11, Name = "aa", Money = 1000, Category = Category.Human, Children = new List<Child> { new Child{ Id = 22, Name = "
bb"} } }; var json = JsonConvert.SerializeObject(entity); } }
Main程式碼

結果:


現在我們不想輸出Id,並且Name換成"名字"

方案一:使用Newtonsoft的原生特性,適用於所有此類序列化輸出都是相同的場景
主要特性

[JsonIgnore]:序列化成字串時,不帶上這個屬性

[JsonProperty]:序列化時,修改屬性名
如:作為模型的類修改如下

public abstract class EntityBase
{
    public virtual long Id { get; set; }
}

public class Entity : EntityBase
{
    [JsonIgnore]
    public override long Id { get; set; }
    [JsonProperty("名字")]
    public string Name { get; set; }
    public decimal Money { get; set; }
    public Category Category { get; set; }
    public List<Child> Children { get; set; }
}

public class Child : EntityBase
{
    [JsonProperty("名字")]
    public string Name { get; set; }
}

public enum Category
{
    Human = 0,
    Cat = 1,
    Dog = 2
}
作為模型的類

然後Main程式碼不用改

結果

這種方案的好處是簡單,壞處就是隻有1種輸出

方案二:自己實現轉化器convertor,適用於任何場景
1、在序列化時,Newtonsoft提供了自定義的方案,只要寫一個類去繼承Newtonsoft.Json.JsonConvertor類即可。

public class MyConvertor : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true; //反序列化時先執行
    }

    public override bool CanRead => false; //使用預設反序列化
    public override bool CanWrite => true;

    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)
    {
        var jObject = new JObject();
        var entity = value as Entity;
        var type = typeof(Entity);
        var props = type.GetProperties();
        foreach (var prop in props)
        {
            var att = prop.GetCustomAttributes(typeof(MyJsonIgnoreAttribute), false).FirstOrDefault();
            if (att != null)
            {
                continue;
            }

            var name = prop.Name;
            var att2 = prop.GetCustomAttributes(typeof(MyJsonPropertyAttribute), false).FirstOrDefault();
            if (att2 != null)
            {
                name = ((MyJsonPropertyAttribute) att2).PropertyName;
            }

            var v = prop.GetValue(entity);
            jObject.Add(name, JToken.FromObject(v));
        }

        jObject.WriteTo(writer);
    }
}

[AttributeUsage(AttributeTargets.Property)]
public class MyJsonIgnoreAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Property)]
public class MyJsonPropertyAttribute : Attribute
{
    public string PropertyName { get; }

    public MyJsonPropertyAttribute(string name)
    {
        PropertyName = name;
    }
}
Convertor類程式碼

注意1:本文只討論序列化,不需要反序列化的場景。所以CanRead=false,並且CanConvert跟ReadJson都沒有實現,如果想使用預設反序列化方案的,CanConvert返回true,CanConvert是表示使用此Convertor類反序列化時,模型物件能否反序列化,網上許多寫法都返回了bool--“當前類是否繼承某個類或實現某個介面來判斷能否反序列化”,但我認為,既然是預設反序列化方案的話,直接返回true即可,除非你也自定義反序列化方案,那就要判斷

public override bool CanConvert(Type objectType)
{
    return objectType.IsAssignableFrom(typeof(EntityBase));
}

注意2:在程式碼中,我使用了自定義的特性來代替原生的JsonIgnoreAttribute和JsonPropertyAttribute,如果你確定只可能有1種輸出但又不想使用預設的序列化方案時,那麼可以不自定義特性,用回原生特性(效果都一樣)。使用自定義特性是為了當有其他的Convertor輸出方案時,可以不一樣。比如某個方案需要序列化json時,帶上Id欄位,某個方案又不帶上,還有個方案要求Name顯示成「名前」...em...一般極少2種及以上的情況的,所以極少需要自定義特性

注意3:在把生成的JObject物件寫到writer中時,網上的部落格大多數寫法是這樣的,各位看官可以驗證這種寫法最終序列化後會多出一次轉義操作,這是錯誤的。stackoverflow網站上的寫法也就是我這種jObject.WriteTo(writer);才是正確的

2、模型類使用特性,參考方案一那樣

補充1:可以在模型類上,使用特性來繫結序列化Convertor,表明我的預設序列化方案就是這個Convertor;當JsonConvert.SerializeObject呼叫時不傳convertor就是使用預設方案

[JsonConverter(typeof(MyConvertor))]

補充2:可以使用入參Formatting.Indented來使序列化的json換行

最後我選擇繼承泛型的JsonConvertor讓更多的模型可以使用這個Convertor,如這裡的Child類使用特性綁定了MyConvertor為預設序列化方案,全部程式碼整合如下:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Json
{
    public class Program
    {
        public static void Main()
        {
            var entity = new Entity
            {
                Id = 11,
                Name = "aa",
                Money = 1000,
                Category = Category.Human,
                Children = new List<Child>
                {
                    new Child{ Id = 22, Name = "bb"}
                }
            };

            var convertor = new MyConvertor<Entity>();
            var json = JsonConvert.SerializeObject(entity, Formatting.Indented, convertor);
        }
    }

    public abstract class EntityBase
    {
        public virtual long Id { get; set; }
    }

    public class Entity : EntityBase
    {
        [MyJsonIgnore]
        public override long Id { get; set; }
        [MyJsonProperty("なまえ")]
        public string Name { get; set; }
        public decimal Money { get; set; }
        public Category Category { get; set; }
        public List<Child> Children { get; set; }
    }

    [JsonConverter(typeof(MyConvertor<Child>))]
    public class Child : EntityBase
    {
        [MyJsonIgnore]
        public override long Id { get; set; }
        [JsonProperty("名字")]
        public string Name { get; set; }
    }

    public enum Category
    {
        Human = 0,
        Cat = 1,
        Dog = 2
    }

    public class MyConvertor<TEntity> : JsonConverter<TEntity>
    {
        public override bool CanRead => false; //使用預設反序列化
        public override bool CanWrite => true;

        public override TEntity ReadJson(JsonReader reader, Type objectType, TEntity existingValue, bool hasExistingValue,
            JsonSerializer serializer)
        {
            throw new NotImplementedException(); //反序列化程式碼
        }

        public override void WriteJson(JsonWriter writer, TEntity entity, JsonSerializer serializer)
        {
            var jObject = new JObject();
            var type = typeof(TEntity);
            var props = type.GetProperties();
            foreach (var prop in props)
            {
                var att = prop.GetCustomAttributes(typeof(MyJsonIgnoreAttribute), false).FirstOrDefault();
                if (att != null)
                {
                    continue;
                }

                var name = prop.Name;
                var att2 = prop.GetCustomAttributes(typeof(MyJsonPropertyAttribute), false).FirstOrDefault();
                if (att2 != null)
                {
                    name = ((MyJsonPropertyAttribute)att2).PropertyName;
                }

                var v = prop.GetValue(entity);
                jObject.Add(name, JToken.FromObject(v));
            }

            jObject.WriteTo(writer);
        }
    }

    [AttributeUsage(AttributeTargets.Property)]
    public class MyJsonIgnoreAttribute : Attribute
    {
    }

    [AttributeUsage(AttributeTargets.Property)]
    public class MyJsonPropertyAttribute : Attribute
    {
        public string PropertyName { get; }

        public MyJsonPropertyAttribute(string name)
        {
            PropertyName = name;
        }
    }
}
全部程式碼

其實特性的邏輯程式碼應該在特性的類裡面寫的,這裡不搞這麼複雜了,因為筆者的文章面向於初學者

執行結果