动态 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_name
- TrySetMember- 设置属性值,例如 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。


