Code Copied

Json.NET丰富多彩的序列化

1.前言

在ASP.NET MVC中提供了JsonResult这样的类,能够让我们直接序列化C#的对象。
在MVC中调用Json(object)这样的方法,将序列化object所有的属性。

在我们使用Json(object)这样的方法时,请考虑一个问题,是否客户端那边需要object的所有信息呢?
如果一个object拥有上百个属性,但客户端只需要其中的3个关键属性Id, Code, Name,用Json(object)将会把这上百个属性都序列化并且返回给客户端。
这明显不是一个好的方法,但幸运的是Json.NET提供了很多灵活的方法来实现这样的需求。

下面我们就从实际的用例开始,分别使用JsonResult和Json.NET来实现这样的需求,并阐明为什么不用JsonResult的原因。

2.用例描述

假如有下面的一个类图:

3个class:Entity, Item, Category。
Entity作为所有实体类的基类。Item类表示物品,Category表示物品类别。

image

然后有以下一些需求:

1. 序列化Categroy对象的Id, Name, Code, CreatedDate属性

2. 序列化Item时,Category引用只序列化Id和Name属性(您甚至可以想象Item拥有100个属性)

当然您可能说,上面的问题很简单,我立马就能想出2个方法出来:
方法1:创建一个ViewModel层,View需要什么字段就给他创建什么样的ViewModel Class。
方法2:使用匿名对象去创建View层需要的字段。

试着想象如下的两个画面,根据需求我们提出以下2个问题。

1. 如何序列化Categroy对象的Id, Name, Code属性?

在Category画面中,我们只显示Category的Id,Code,Name,CreatedDate字段,但是不显示Description字段。

image

2. 序列化Item时,如何只序列化物品本身的值类型属性以及物品类别的Name字段?

在Item画面中��只显示Item的Id,Code,Name,Unit,SouceArea,CreatedDate和Category的Name字段,不显示Category的其他字段。

image

3.使用JsonResult的实现

为了引出后续的结论,先使用这两个方法来解决这两个问题。通过代码我们一定能够发现这两种方法的一些缺陷。

3.1序列化ViewModel Class

CategoryViewModel:

public class CategoryViewModel
{
    private readonly Category _category;

    public CategoryViewModel(Category category)
    {
        _category = category;
    }

    public int Id { get { return _category.Id; } }
    public string Code { get { return _category.Code; } }
    public string Name { get { return _category.Name; } }
    public DateTime CreatedDate
    {
        get { return _category.CreatedDate; }
    }
}

通过调用Json(object)方法,输出的json串如下:

{
    "Id": 1,
    "Code": "0001",
    "Name": "办公文具",
    "CreatedDate": "/Date(1400596685227)/"
}

ItemViewModel:

public class ItemViewModel
{
    private readonly Item _item;

    public ItemViewModel(Item item)
    {
        _item = item;
    }

    public int Id { get { return _item.Id; } }
    public string Code { get { return _item.Code; } }
    public string Name { get { return _item.Name; } }
    public string Unit { get { return _item.Unit; } }
    public string SourceArea { get { return _item.SourceArea; } }
    public DateTime CreatedDate
    {
        get { return _item.CreatedDate; }
    }
    public object Category
    {
        get { return new {Id = _item.Category.Id, Name = _item.Category.Name}; }
    }
}

通过调用Json(object)方法,输出的json串如下:

{
    "Id": 1,
    "Code": "00000001",
    "Name": "笔记本",
    "Unit": "本",
    "SourceArea": "深圳",
    "CreatedDate": "/Date(-62135596800000)/",
    "Category": {
        "Id": 1,
        "Name": "办公文具"
    }
}

3.2构建匿名对象返回json

GetCategoryJsonResult:

public JsonResult GetCategoryJsonResult()
{
    var category = new Category()
    {
        Id = 1,
        Code = "0001",
        Name = "办公文具",
        CreatedDate = DateTime.Now,
        Description = "Some Descriptions..."
    };
    return Json(new {Id = category.Id, Code = category.Code, Name = category.Name, CreatedDate = category.CreatedDate},
        JsonRequestBehavior.AllowGet);
}

GetItemJsonResult:

public JsonResult GetItemJsonResult()
{
    var category = new Category()
    {
        Id = 1,
        Code = "0001",
        Name = "办公文具",
        CreatedDate = DateTime.Now
    };
    var item = new Item()
    {
        Id = 1,
        Code = "00000001",
        Name = "笔记本",
        Unit = "本",
        SourceArea = "深圳",
        Category = category
    };
    return
        Json(
            new
            {
                Id = item.Id,
                Code = item.Code,
                Name = item.Name,
                Unit = item.Unit,
                SourceArea = item.SourceArea,
                CreatedDate = item.CreatedDate,
                Category = new {Id = item.Category.Id, Name = item.Category.Name}
            }, JsonRequestBehavior.AllowGet);
}

调用以上2个方法和ViewModel返回的json结果是一样的。

3.3使用JsonResult序列化的问题

上面的代码确实没什么问题,也确实能够满足我们的需求。问题在于上面的代码中出现了“重复”
CategoryModelClass和ItemModel类的内容确实不一样,GetCategoryJsonResult()和GetItemJsonResult()方法的内容也确实不一样。

仔细比较这些代码,我们仍然能发现以下3个问题:
它们的写法太相似了,试想如果将来项目中的Entity增长至100个甚至1000个,是否意味着项目中有很多这种“神似”的代码。
如果一个Entity的字段有非常多的字段,则需要在View Model Class中添加很多字段,或在GetEntityJsonResult()方法中创建一个很长的匿名对象。
在需求变更时,变更一个Entity类,就有可能需要去变更对应的View Model Class或GetEntityJsonResult()方法。

从实际的业务去分析,不难发现以下两点:

在Category或Item的画面中,它们本身的值类型字段基本都需要序列化
在Item的画面中,引用的Category只需要序列化关键的Id和Name字段

4.使用Json.NET实现

Json.NET提供了Ignore特性,应用Ignore特性的属性将不会被序列化。但是当Entity的数量较多时,选择Ignore特性并不是一个很好的方法。

4.1安装Json.NET

按照如下步骤去安装Json.NET,PM命令:

Install-Package Newtonsoft.Json

image

4.2使用自定义ContractResolver

定义DynamicContractResolver类:

public class DynamicContractResolver : DefaultContractResolver
{
    // 需要序列化的属性列表
    private readonly IList<string> _propertiesToSerialize = null;

    public DynamicContractResolver(IList<string> propertiesToSerialize)
    {
        _propertiesToSerialize = propertiesToSerialize;
    }

    /// <summary>
    /// 重写创建属性方法
    /// </summary>
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
        return properties.Where(p => _propertiesToSerialize.Contains(p.PropertyName)).ToList();
    }
}

序列化时调用DynamicContractResolver实例:

class Program
{
    static void Main(string[] args)
    {

        IList<string> propertiesToSerialize = new List<string> { "Id", "Code", "Name" };

        var category = new Category()
        {
            Id = 1,
            Code = "0001",
            Name = "办公文具",
            CreatedDate = DateTime.Now,
            Description = "Some Descriptions..."
        };

        string categoryJson = JsonConvert.SerializeObject(category, Formatting.Indented, new JsonSerializerSettings()
        {
            ContractResolver = new DynamicContractResolver(propertiesToSerialize)
        });

        System.Console.WriteLine(categoryJson);

        System.Console.ReadKey();
    }
}

输出结果:

image

4.3使用自定义JsonConverter

定义KeyConverter类:

public class KeyConverter : JsonConverter
{
    /// <summary>
    /// 关联对象序列化的属性列表
    /// </summary>
    private readonly IList<string> _referenceKeyProperties;
    public KeyConverter(params string[] referenceKeyProperties)
    {
        _referenceKeyProperties = referenceKeyProperties;
    }

    /// <summary>
    /// 只有当对象类型为Entity类型时,可以进行序列化
    /// </summary>
    public override bool CanConvert(Type objectType)
    {
        return (objectType.BaseType == typeof(Entity));
    }

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

    /// <summary>
    /// 重写WriteJson方法
    /// </summary>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {

        writer.WriteStartObject();
        foreach (MemberInfo mi in value.GetType().GetMembers(BindingFlags.GetField | BindingFlags.Instance | BindingFlags.Public))
        {
            var p = mi as PropertyInfo;

            if (p != null && p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Length == 0)
            {
                var propertyType = p.PropertyType;

                // 判定是否为关联属性,如果是关联属性,则只序列化关键的属性
                if (propertyType.BaseType == typeof(Entity))
                {
                    writer.WritePropertyName(propertyType.Name);
                    writer.WriteStartObject();
                    foreach (PropertyInfo refPropertyInfo in propertyType.GetProperties())
                    {
                        if (_referenceKeyProperties.Contains(refPropertyInfo.Name))
                        {
                            writer.WritePropertyName(refPropertyInfo.Name);
                            serializer.Serialize(writer, refPropertyInfo.GetValue(p.GetValue(value), new object[] { }));
                        }
                    }
                    writer.WriteEndObject();
                }
                else
                {
                    writer.WritePropertyName(p.Name);
                    serializer.Serialize(writer, p.GetValue(value, new object[] { }));
                }

            }
        }
        writer.WriteEndObject();
    }
}

序列化时调用KeyConverter实例:

class Program
{
    private static void Main(string[] args)
    {

        IList<string> propertiesToSerialize = new List<string> {"Id", "Code", "Name"};

        var category = new Category()
        {
            Id = 1,
            Code = "0001",
            Name = "办公文具",
            CreatedDate = DateTime.Now,
            Description = "Some Descriptions..."
        };
        var item = new Item()
        {
            Id = 1,
            Code = "00000001",
            Name = "笔记本",
            Unit = "本",
            SourceArea = "深圳",
            Category = category,
            CreatedDate = DateTime.Now
        };

        string categoryJson = JsonConvert.SerializeObject(category, Formatting.Indented,
            new JsonSerializerSettings()
            {
                ContractResolver = new DynamicContractResolver(propertiesToSerialize)
            });

        string itemJson = JsonConvert.SerializeObject(item, Formatting.Indented,
            new KeyConverter(propertiesToSerialize.ToArray()));

        System.Console.WriteLine("通过自定义ContractResolver只序列化Category的Id, Code, Name属性:");
        System.Console.WriteLine(categoryJson);
        System.Console.WriteLine("通过自定义JsonConverter序列化Item的值类型属性,并且只序列化Category的Id, Code, Name属性:" );
        System.Console.WriteLine(itemJson);

        System.Console.ReadKey();
    }
}

输出结果:

image

4.4源代码下载

源码:http://blog.64cm.com/source/RichJsonNetSerialization.zip