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;
        }
    }
}
全部代碼

其實特性的邏輯代碼應該在特性的類里面寫的,這里不搞這么復雜了,因為筆者的文章面向於初學者

運行結果

 

實用封裝:

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

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

            var entity2 = new Entity
            {
                Id = 11,
                Name = "aa",
                Money = 1000,
                Category = Category.Human
            };

            var entities = new EntityBase[] { entity, entity2 };
            var convertor = new MyConvertor();
            var json = JsonConvert.SerializeObject(entities, Formatting.Indented, convertor);
        }
    }

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

    public class Entity : EntityBase
    {
        public override long Id { get; set; }
        [MyJsonProperty("なまえ")]
        [MyJsonIgnore(true)]
        public string Name { get; set; }
        [MyJsonIgnore(true)]
        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
    }

    public class MyConvertor : JsonConverter
    {
        public override bool CanRead => false; //使用默認反序列化
        public override bool CanWrite => true;

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var type = value.GetType();
            if (type.IsIEnumerable())
            {
                var jArray = new JArray();
                var items = (IEnumerable)value;
                foreach (var item in items)
                {
                    var temp = JsonConvert.SerializeObject(item, this);
                    jArray.Add(JsonConvert.DeserializeObject(temp));
                }

                jArray.WriteTo(writer);
            }
            else
            {
                var jObject = new JObject();
                var props = type.GetProperties();
                foreach (var prop in props)
                {
                    var ignoreAttribute = prop.GetAttribute<MyJsonIgnoreAttribute>();
                    if (ignoreAttribute != null && !ignoreAttribute.OnlyNull)
                    {
                        continue;
                    }

                    var name = prop.Name;
                    var propertyAttribute = prop.GetAttribute<MyJsonPropertyAttribute>();
                    if (propertyAttribute != null)
                    {
                        name = propertyAttribute.PropertyName;
                    }

                    object val;
                    if (prop.PropertyType.IsFundamental())
                    {
                        val = prop.GetValue(value);
                    }
                    else
                    {
                        var propValue = prop.GetValue(value);
                        var temp = JsonConvert.SerializeObject(propValue, this);
                        val = JsonConvert.DeserializeObject(temp);
                    }

                    if (val == null)
                    {
                        if (ignoreAttribute == null)
                        {
                            jObject.Add(name, null);
                        }

                        continue;
                    }

                    jObject.Add(name, JToken.FromObject(val));
                }

                jObject.WriteTo(writer);
            }
        }

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

        public override bool CanConvert(Type objectType)
        {
            return true;
        }
    }

    public static class AttributeExtension
    {
        public static TAttribute GetAttribute<TAttribute>(this PropertyInfo prop) where TAttribute : Attribute
        {
            var obj = prop.GetCustomAttributes(typeof(TAttribute), false).FirstOrDefault();
            var result = (TAttribute)obj;
            return result;
        }
    }

    public static class TypeExtension
    {
        /// <summary>
        /// 判斷類型是否是基礎類型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsFundamental(this Type type)
        {
            if (type.IsNullable())
            {
                return IsFundamental(Nullable.GetUnderlyingType(type));
            }

            var result = type.IsPrimitive || type == typeof(decimal) || type == typeof(string) || type.IsEnum ||
                         type == typeof(DateTime) || type == typeof(TimeSpan);
            return result;
        }

        /// <summary>
        /// 判斷類型是否是列表或數組
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsIEnumerable(this Type type)
        {
            var result = typeof(IEnumerable).IsAssignableFrom(type);
            return result;
        }

        /// <summary>
        /// 是否是可空類型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsNullable(this Type type)
        {
            var result = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
            return result;
        }
    }

    [AttributeUsage(AttributeTargets.Property)]
    public class MyJsonIgnoreAttribute : Attribute
    {
        public bool OnlyNull { get; set; }

        public MyJsonIgnoreAttribute(bool onlyNull = false)
        {
            OnlyNull = onlyNull;
        }
    }

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

        public MyJsonPropertyAttribute(string name)
        {
            PropertyName = name;
        }
    }
}
實用封裝

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM