博 之 文

以 拼 搏 设 计 梦 想 , 以 恒 心 编 程 明 天
  首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

NET 4.0 动态JSON解析器 (DynamicJsonObject)

Posted on 2013-01-25 11:19  IsNull_Soft  阅读(600)  评论(0)    收藏  举报
简介

其实已经有许多用来解析JSON格式数据的库了,为什么我们还想要再创造一个呢?

因为.NET 4.0框架引入了一个新的类型:dynamic!

背景

dynamic实际上是一个静态类型,但是编译器看待它与其它的类型不同。编译器遇到dynamic类型时不会作任何的类型安全检查(绕过了静态类型检查)

例如:
view source
print?
01     class Program
02     {
03         static void Main(string[] args)
04         {
05             func(1); // 'int' does not contain a definition for 'error'
06         }
07         static void func(dynamic obj)
08         {
09             obj.error = "oops";
10         }
11     }

上面这程序调用带有一个int型变量的func函数,当然,int类型没有一个名为error属性,但是程序在编译时不会产生任何错误。而一切在运行时便看起来有所不同了。会抛出出错信息为'int'不包含'error'的定义的RuntimeBinderException异常。

DynamicObject

加入了dynamic功能的.NET层叫做Dynamic Language Runtime(DLR)。DLR处于Common Language Runtime(CLR)的顶部。动态对象都实现了IDynamicMetaObjectProvider接口。

DynamicObject是一个实现了IDynamicMetaObjectProvider的抽象类并提供一系列的基本操作。继承自DynamicObject的一个类可以重载例如TrySetMember以及TryGetMember方法来set和get属性。

以下是DynamicObject几个比较重要的可重载的成员函数,以实现对动态类型的自定义表现。

TryBinaryOperation  - 二进制操作 *,+,-...

TryUnaryOperation  - 一元操作 --,++,...

TryGetIndex  - 以索引访问一个对象操作 []

TrySetIndex  - 以索引设置一个对象操作 []

TryGetMember  - 获取一个属性值,例如:obj.property_name

TrySetMember  - 设置一个属性值,例如:obj.property_name = "value"

TryInvokeMember  - 调用一个方法,例如:obj.SomeMethod(...)

以下是一个实现了所有上述方法的例子:
view source
print?
001     public class DynamicConsoleWriter : DynamicObject
002     {
003         protected string first = "";
004         protected string last  = "";
005         public int Count
006         {
007             get
008             {
009                 return 2;
010             }
011         }
012         public override bool TryBinaryOperation(BinaryOperationBinder binder, 
013                              object arg, out object result)
014         {
015             bool success = false;
016             if (binder.Operation == System.Linq.Expressions.ExpressionType.Add)
017             {
018                 Console.WriteLine("I have to think about that");
019                 success = true;
020             }
021             result = this;
022             return success;
023         }
024         public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result)
025         {
026             bool success = false;
027             if (binder.Operation == System.Linq.Expressions.ExpressionType.Increment)
028             {
029                 Console.WriteLine("I will do it later");
030                 success = true;
031             }
032             result = this;
033             return success;
034         }
035         public override bool TryGetIndex(GetIndexBinder binder, 
036                         object[] indexes, out object result)
037         {
038             result = null;
039             if ( (int)indexes[0] == 0)
040             {
041                 result = first;
042             }
043             else if ((int)indexes[0] == 1)
044             {
045                 result = last;
046             }
047             return true;
048         }
049         public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
050         {
051             if ((int)indexes[0] == 0)
052             {
053                 first = (string)value;
054             }
055             else if ((int)indexes[0] == 1)
056             {
057                 last = (string)value;
058             }
059             return true;
060         }
061         public override bool TryGetMember(GetMemberBinder binder, out object result)
062         {
063             string name    = binder.Name.ToLower();
064             bool   success = false;
065             result = null;
066             if (name == "last")
067             {
068                 result = last;
069                 success = true;
070             }
071             else if (name == "first")
072             {
073                 result = first;
074                 success = true;
075             }
076             return success;
077         }
078         public override bool TrySetMember(SetMemberBinder binder, object value)
079         {
080             string name    = binder.Name.ToLower();
081             bool   success = false;
082             if (name == "last")
083             {
084                 last = (string)value;
085                 success = true;
086             }
087             else if (name == "first")
088             {
089                 first = (string)value;
090                 success = true;
091             }
092             return success;
093         }
094         public override bool TryInvokeMember(InvokeMemberBinder binder, 
095                         object[] args, out object result)
096         {
097             string name = binder.Name.ToLower();
098             bool success = false;
099             result = true;
100             if (name == "writelast")
101             {
102                 Console.WriteLine(last);
103                 success = true;
104             }
105             else if (name == "writefirst")
106             {
107                 Console.WriteLine(first);
108                 success = true;
109             }
110             return success;
111         }
112     }

以下展示了我们如何使用它们:
view source
print?
01     dynamic dynamicConsoleWriter = new DynamicConsoleWriter();
02     dynamicConsoleWriter.First = "I am just a"; // TrySetMember is invoked
03     dynamicConsoleWriter.Last = " Lion!";       // TrySetMember is invoked 
04       
05     var result1 = dynamicConsoleWriter + 2;     // TryBinaryOperation is invoked
06     var result2 = ++dynamicConsoleWriter;       // TryUnaryOperation is invoked
07     dynamicConsoleWriter[0] = "Hello";          // TrySetIndex is invoked
08     var result3 = dynamicConsoleWriter[0];      // TryGetIndex is invoked
09     var result4 = dynamicConsoleWriter.First;   // TryBinaryOperation is invoked
10     var result5 = dynamicConsoleWriter.Last;    // TryBinaryOperation is invoked 
11     var result6 = dynamicConsoleWriter.Count;   // DynamicConsoleWriter Count property is called
12       
13     dynamicConsoleWriter.WriteFirst();          // TryInvokeMember is invoked
14     dynamicConsoleWriter.WriteLast();           // TryInvokeMember is invoked

另外一个动态类型很酷的特性就是他们实现了专一性,也就是说,大部分特定的函数调用在运行时将会被选择。

当遇到类型找不到时将会抛出RuntimeBinderException异常。该异常可通过实现一个接受object值的函数来避免。
view source
print?
01     public class Specificity
02     {
03         public static void printDynamic(dynamic obj)
04         {
05             print(obj);
06         }
07         protected static void print(List<int> list)
08         {
09             foreach (var item in list)
10             {
11                 Console.WriteLine(item);
12             }
13         }
14         protected static void print(object obj)
15         {
16             Console.WriteLine("I do not know how to print you");
17         }
18     }

当我们传递任何参数至printDynamic函数除了List<int>时,print(object obj)将会被调用。

动态JSON转换器

JavaScriptSerializer将会把一个JSON字符串转换至一个IDictionary<string,object>中。

JavaScriptSerializer在System.Web.Extensions中声明,使用System.Web.Script.Serialization来编译代码。
view source
print?
1     var serializer = new JavaScriptSerializer();
2     serializer.RegisterConverters(new[] { new DynamicJsonConverter() }); 
3     dynamic data = serializer.Deserialize<object>(json);

serializer。Deserialize<object>(json)转换JSON字符串并调用JavaScriptConverter的Deserialize方法,我们重载此方法来从Deserialize方法中提供的dictionary创建新的DynamicJsonObjec。

DynamicObject如魔法搬将一个dictionary转换为包含所有JSON属性的对象。

ExpandoObject是一个新类但却是不是我们需要的,它提供的无法满足我们更加灵活的需求。

每个序列化的dictionary中的值是一个简单类型(也就是int,string,double),IDictionary<string,object>({...})或者ArrayList。

我们重载了DynamicObject的TryGetMember函数来处理所有这三种类型的序列化dictionary值。

同样我们也会实现TrySetMember方法以添加新的域到JSON对象中,并且将实现IEnumerable接口来实现对动态JSON对象的简单迭代。

以下便是如何使用动态解析器的例子:
view source
print?
01     const string json =
02         "{" +
03         "     \"firstName\": \"John\"," +
04         "     \"lastName\" : \"Smith\"," +
05         "     \"age\"      : 25," +
06         "     \"address\"  :" +
07         "     {" +
08         "         \"streetAddress\": \"21 2nd Street\"," +
09         "         \"city\"         : \"New York\"," +
10         "         \"state\"        : \"NY\"," +
11         "         \"postalCode\"   : \"11229\"" +
12         "     }," +
13         "     \"phoneNumber\":" +
14         "     [" +
15         "         {" +
16         "           \"type\"  : \"home\"," +
17         "           \"number\": \"212 555-1234\"" +
18         "         }," +
19         "         {" +
20         "           \"type\"  : \"fax\"," +
21         "           \"number\": \"646 555-4567\"" +
22         "         }" +
23         "     ]" +
24         " }";
25       
26     var serializer = new JavaScriptSerializer();
27     serializer.RegisterConverters(new[] { new DynamicJsonConverter() });
28     dynamic data = serializer.Deserialize<object>(json);
29     Console.WriteLine(data.firstName);           // John
30     Console.WriteLine(data.lastName);            // Smith
31     Console.WriteLine(data.age);                 // 25
32     Console.WriteLine(data.address.postalCode);  // 11229
33     Console.WriteLine(data.phoneNumber.Count);   // 2
34     Console.WriteLine(data.phoneNumber[0].type); // home
35     Console.WriteLine(data.phoneNumber[1].type); // fax
36     foreach (var pn in data.phoneNumber)
37     {
38         Console.WriteLine(pn.number);            // 212 555-1234, 646 555-4567
39     }
40     Console.WriteLine(data.ToString());
41       
42     // and creating JSON formatted data
43     dynamic jdata   = new DynamicJsonObject();
44     dynamic item1   = new DynamicJsonObject();
45     dynamic item2   = new DynamicJsonObject();
46     ArrayList items = new ArrayList();
47     item1.Name  = "Drone";
48     item1.Price = 92000.3;
49     item2.Name  = "Jet";
50     item2.Price = 19000000.99;
51     items.Add(item1);
52     items.Add(item2);
53     jdata.Date  = "06/06/2004";
54     jdata.Items = items;
55     Console.WriteLine(jdata.ToString());

鸣谢

初始化动态JSON转换器是由Shawn Weisfeld编写

License

此文章涉及到的任何源代码以及文件都在The Code Project Open License (CPOL)证书公开