简介
其实已经有许多用来解析JSON格式数据的库了,为什么我们还想要再创造一个呢?
因为.NET 4.0框架引入了一个新的类型:dynamic!
背景
dynamic实际上是一个静态类型,但是编译器看待它与其它的类型不同。编译器遇到dynamic类型时不会作任何的类型安全检查(绕过了静态类型检查)
例如:
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(...)
以下是一个实现了所有上述方法的例子:
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 }
以下展示了我们如何使用它们:
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值的函数来避免。
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来编译代码。
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对象的简单迭代。
以下便是如何使用动态解析器的例子:
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