22
2014
10

把JSON反序列化成具体的子类

 

    做接口开发时,我们常常会用到序列化之类的操作,一般情况下都比较简单,用系统自带的类库或第三方组件可以很轻松的搞定。最近做微信接口时遇到这样一个情况,对方返回的JSON很灵活,我们用模型处理的话就是用子类实现的多态,具体到微信那里是查询货架,为了叙述简便,我们这里用简单的例子说明。

假定对方返回我们的格式如:

{

    "ID":23,

    "Name":"The Name",

    "Details":

    [

        {"Type":1,"Name":"Type1 Name","Attr1":2},

        {"Type":2,"Name":"Type2 name","Attr2":3}

    ]

}

可以看到,Details是个数组,里面的两个对象还有点不一样,第一个有"Attr1"属性,第二个有"Attr2"属性,按照常规做法,我们先定义出模型类:

public class M
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public List<Detail> Details { get; set; }
        /// <summary>
        /// 基类
        /// </summary>
        public class Detail
        {
            public int Type { get; set; }
            public string Name { get; set; }
        }
        /// <summary>
        /// 子类1
        /// </summary>
        public class Detail1 : Detail
        {
            public int Attr1 { get; set; }
        }
        /// <summary>
        /// 子类2
        /// </summary>
        public class Detail2 : Detail
        {
            public int Attr2 { get; set; }
        }
    }

然后我们写点测试代码(我用的的Newtonsoft.Json这个第三方的组件)

static void Main(string[] args)
    {
        var json = "{" +
                        "\"ID\":23," +
                        "\"Name\":\"The Name\"," +
                        "\"Details\":" +
                        "[" +
                            "{\"Type\":1,\"Name\":\"Type1 Name\",\"Attr1\":2}," +
                            "{\"Type\":2,\"Name\":\"Type2 name\",\"Attr2\":3}" +
                        "]" +
                    "}";
        var m = JsonConvert.DeserializeObject<M>(json);
    }

Ctrl+F5运行之,没有异常,出现了我们喜欢的、只有唯一一行的"请按任意键继续。。。",可是真的可以欢喜了吗?我们调试看看Details到底是什么!

这下明白了吧,它根本就没有反序列化成具体子类,而都是用基类代替了,这点比较容易忽视的,至少我开始就忽视了。这也难怪,你不给人家说明反序列化成哪个子类,人家怎么会知道呢?这样的特殊需求类库当然早就支持了,这里对比说明一下:

JavaScriptSerializer无法实现反序列化成具体子类
DataContractSerializer 在基类加[KnowType(子类类型)],附加__type属性
Newtonsoft.Json

自定义SerializationBinder子类(不是必须的),

设置JsonSerializerSettings的TypeNameHandling = TypeNameHandling.Auto,Binder为自定义的SerializationBinder子类或直接是默认SerializationBinder

附加$type属性

我们想反序列化成具体子类,就要知道什么样的格式才可以反序列化成具体子类,这个简单,我们填充好M模型,序列化看一下得到的json就可以了:

static void Main(string[] args)
    {
        var m = new M
        {
            ID = 23,
            Name = "The Name",
            Details = new List<M.Detail>
            {
                new M.Detail1{ Type= 1, Name="Type1 Name", Attr1=2},
                new M.Detail2{ Type= 2, Name="Type2 Name", Attr2=3},
            }
        };
        var json = JsonConvert.SerializeObject(m, Formatting.Indented, new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.Auto,
        });
        Console.WriteLine(json);
    }

  

可以看到,在子类多态时,它加了一个$type属性,属性的值便是这个具体子类型的信息了。所以我们要做的就是修改原来的JSON字符串,使其成为上面的格式,用Newtonsoft.Json可以这样实现: 

static void Main(string[] args)
    {
        //原json
        var json = "{" +
                        "\"ID\":23," +
                        "\"Name\":\"The Name\"," +
                        "\"Details\":" +
                        "[" +
                            "{\"Type\":1,\"Name\":\"Type1 Name\",\"Attr1\":2}," +
                            "{\"Type\":2,\"Name\":\"Type2 name\",\"Attr2\":3}" +
                        "]" +
                    "}";
        var jtoken =JToken.Parse(json);
        var details = jtoken.SelectToken("Details");
        foreach (JToken obj in details)
        {
            JProperty jp = new JProperty("$type", "RunOnce.M+Detail" + obj.Value<int>("Type").ToString() + ", RunOnce");
            obj.First.AddBeforeSelf(jp);//一定要加到第一个位置,不然不起作用
        }
        //修改后
        json = jtoken.ToString();
        Console.WriteLine(json);
        //可以正确得到子类
        var m1 = JsonConvert.DeserializeObject<M>(json, new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.Auto,
        });
    }

  

上面用到了JToken的SelectToken方法,还有个SelectTokens方法查询多个JToken的,参数都为jpath表达式,用法如下(js如何访问字符串就如何写,*可以代指所有):

"Details[0]"; //{Type:1,Name="Type1 Name"}

"Details[*]"; //[{Type:1,Name="Type1 Name"},{Type:2,Name="Type2 Name"}]

"*.attr.list[2]" //对象attr属性的list第二个元素

 

« 上一篇下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。