动态 JSON 解析器






4.89/5 (21投票s)
一个简单的 JSON 解析器。
引言
有很多库可以解析 JSON 格式的数据,那么我们为什么要创建另一个呢?因为 .NET 4.0 Framework 引入了一个新类型 - dynamic
!
背景
dynamic
实际上是一个静态类型,但编译器对它的处理方式与其他类型不同。编译器在遇到动态类型时不会进行任何类型安全检查(它会绕过静态类型检查)。
例如
class Program
{
static void Main(string[] args)
{
func(1); // 'int' does not contain a definition for 'error'
}
static void func(dynamic obj)
{
obj.error = "oops";
}
}
上面的程序使用 int
类型的参数调用 func
,当然,int
类型没有名为 error
的属性,但程序在编译时不会生成任何错误。当我们运行程序时,情况就有所不同了。会抛出 RuntimeBinderException
异常,消息为 'int' does not contain a definition for 'error'。
动态对象
添加动态功能的 .NET 层称为动态语言运行时 (DLR)。DLR 位于公共语言运行时 (CLR) 之上。动态对象由 IDynamicMetaObjectProvider
接口表示。
DynamicObject
是一个抽象类,它实现了 IDynamicMetaObjectProvider
并提供了一组可以在动态对象上执行的基本操作。例如,继承自DynamicObject
的类可以覆盖 TrySetMember
和 TryGetMember
函数,用于设置和获取属性。
以下是 DynamicObject
中更重要的成员,可以重写它们以实现动态对象的期望自定义行为
TryBinaryOperation
- 二元运算 *, +, - ...TryUnaryOperation
- 一元运算 --, ++, - ...TryGetIndex
- 通过索引 [] 访问对象的操作TrySetIndex
- 通过索引 [] 设置值的操作TryGetMember
- 获取属性值,例如 obj.property_nameTrySetMember
- 设置属性值,例如 obj.property_name = "value"TryInvokeMember
- 调用方法,例如 obj.SomeMethod(...)
以下是上面列出的所有方法的示例实现。
public class DynamicConsoleWriter : DynamicObject
{
protected string first = "";
protected string last = "";
public int Count
{
get
{
return 2;
}
}
public override bool TryBinaryOperation(BinaryOperationBinder binder,
object arg, out object result)
{
bool success = false;
if (binder.Operation == System.Linq.Expressions.ExpressionType.Add)
{
Console.WriteLine("I have to think about that");
success = true;
}
result = this;
return success;
}
public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result)
{
bool success = false;
if (binder.Operation == System.Linq.Expressions.ExpressionType.Increment)
{
Console.WriteLine("I will do it later");
success = true;
}
result = this;
return success;
}
public override bool TryGetIndex(GetIndexBinder binder,
object[] indexes, out object result)
{
result = null;
if ( (int)indexes[0] == 0)
{
result = first;
}
else if ((int)indexes[0] == 1)
{
result = last;
}
return true;
}
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
{
if ((int)indexes[0] == 0)
{
first = (string)value;
}
else if ((int)indexes[0] == 1)
{
last = (string)value;
}
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string name = binder.Name.ToLower();
bool success = false;
result = null;
if (name == "last")
{
result = last;
success = true;
}
else if (name == "first")
{
result = first;
success = true;
}
return success;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
string name = binder.Name.ToLower();
bool success = false;
if (name == "last")
{
last = (string)value;
success = true;
}
else if (name == "first")
{
first = (string)value;
success = true;
}
return success;
}
public override bool TryInvokeMember(InvokeMemberBinder binder,
object[] args, out object result)
{
string name = binder.Name.ToLower();
bool success = false;
result = true;
if (name == "writelast")
{
Console.WriteLine(last);
success = true;
}
else if (name == "writefirst")
{
Console.WriteLine(first);
success = true;
}
return success;
}
}
以下是我们如何使用它
dynamic dynamicConsoleWriter = new DynamicConsoleWriter();
dynamicConsoleWriter.First = "I am just a"; // TrySetMember is invoked
dynamicConsoleWriter.Last = " Lion!"; // TrySetMember is invoked
var result1 = dynamicConsoleWriter + 2; // TryBinaryOperation is invoked
var result2 = ++dynamicConsoleWriter; // TryUnaryOperation is invoked
dynamicConsoleWriter[0] = "Hello"; // TrySetIndex is invoked
var result3 = dynamicConsoleWriter[0]; // TryGetIndex is invoked
var result4 = dynamicConsoleWriter.First; // TryBinaryOperation is invoked
var result5 = dynamicConsoleWriter.Last; // TryBinaryOperation is invoked
var result6 = dynamicConsoleWriter.Count; // DynamicConsoleWriter Count property is called
dynamicConsoleWriter.WriteFirst(); // TryInvokeMember is invoked
dynamicConsoleWriter.WriteLast(); // TryInvokeMember is invoked
动态类型的另一个很酷的特性是它们实现了特异性,即,将在运行时选择最具体的函数调用。
如果未找到合适的类型,则会抛出 RuntimeBinderException
。可以通过实现一个接受对象的函数来避免此异常。
public class Specificity
{
public static void printDynamic(dynamic obj)
{
print(obj);
}
protected static void print(List<int> list)
{
foreach (var item in list)
{
Console.WriteLine(item);
}
}
protected static void print(object obj)
{
Console.WriteLine("I do not know how to print you");
}
}
如果我们向 printDynamic
函数传递任何内容而不是 List<int>
,则会调用 print(object obj)
。
动态 JSON 转换器
JavaScriptSerializer 将完成将 JSON 字符串转换为 IDictionary<string, object>
的实际工作。
JavaScriptSerializer
位于 System.Web.Extensions 程序集中,并且需要使用 System.Web.Script.Serialization
才能编译代码。
var serializer = new JavaScriptSerializer(); serializer.RegisterConverters(new[] { new DynamicJsonConverter() }); dynamic data = serializer.Deserialize<object>(json);
serializer.Deserialize<object>(json)
解析 JSON 字符串并调用 JavaScriptConverter
的 Deserialize
方法,我们重写此方法以从 Deserialize
方法提供的字典创建一个新的 DynamicJsonObject
。
DynamicObject
是将字典转换为一个很棒的对象的魔力,该对象具有所有 JSON 字段作为属性。
ExpandoObject
是一个新类,它完全执行此操作,但它不适用于我们,因为我们需要比它提供的更多灵活性。
反序列化字典中的每个值都是简单类型(即 int
、string
、double
,...)、IDictionary<string, object>
(即 {...})或 ArrayList
。
我们重写了 DynamicObject
的 TryGetMember
函数以处理反序列化字典的所有 3 种类型的值。
我们还将抛出一个 TrySetMember
的实现,以允许向我们的 JSON 对象添加新字段,并且还将实现 IEnumerable
以允许轻松迭代动态 JSON 对象。
以下是我们如何使用我们的动态解析器
const string json =
"{" +
" \"firstName\": \"John\"," +
" \"lastName\" : \"Smith\"," +
" \"age\" : 25," +
" \"address\" :" +
" {" +
" \"streetAddress\": \"21 2nd Street\"," +
" \"city\" : \"New York\"," +
" \"state\" : \"NY\"," +
" \"postalCode\" : \"11229\"" +
" }," +
" \"phoneNumber\":" +
" [" +
" {" +
" \"type\" : \"home\"," +
" \"number\": \"212 555-1234\"" +
" }," +
" {" +
" \"type\" : \"fax\"," +
" \"number\": \"646 555-4567\"" +
" }" +
" ]" +
" }";
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new DynamicJsonConverter() });
dynamic data = serializer.Deserialize<object>(json);
Console.WriteLine(data.firstName); // John
Console.WriteLine(data.lastName); // Smith
Console.WriteLine(data.age); // 25
Console.WriteLine(data.address.postalCode); // 11229
Console.WriteLine(data.phoneNumber.Count); // 2
Console.WriteLine(data.phoneNumber[0].type); // home
Console.WriteLine(data.phoneNumber[1].type); // fax
foreach (var pn in data.phoneNumber)
{
Console.WriteLine(pn.number); // 212 555-1234, 646 555-4567
}
Console.WriteLine(data.ToString());
// and creating JSON formatted data
dynamic jdata = new DynamicJsonObject();
dynamic item1 = new DynamicJsonObject();
dynamic item2 = new DynamicJsonObject();
ArrayList items = new ArrayList();
item1.Name = "Drone";
item1.Price = 92000.3;
item2.Name = "Jet";
item2.Price = 19000000.99;
items.Add(item1);
items.Add(item2);
jdata.Date = "06/06/2004";
jdata.Items = items;
Console.WriteLine(jdata.ToString());
致谢
最初的动态 JSON 转换器由 Shawn Weisfeld 编写。
历史
- 2012 年 3 月 21 日:更新了源代码。使用
JavaScriptSerializer
将DynamicJsonObject
转换为字符串。 - 2012 年 3 月 23 日:更新了源代码。添加
DynamicJsonObjectConverter
以正确序列化DynamicJsonObject
。