65.9K
CodeProject 正在变化。 阅读更多。
Home

动态 JSON 解析器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (21投票s)

2012年3月19日

CPOL

3分钟阅读

viewsIcon

121234

downloadIcon

2969

一个简单的 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的类可以覆盖 TrySetMemberTryGetMember 函数,用于设置和获取属性。 

以下是 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 字符串并调用 JavaScriptConverterDeserialize 方法,我们重写此方法以从 Deserialize 方法提供的字典创建一个新的 DynamicJsonObject

DynamicObject 是将字典转换为一个很棒的对象的魔力,该对象具有所有 JSON 字段作为属性。

ExpandoObject 是一个新类,它完全执行此操作,但它不适用于我们,因为我们需要比它提供的更多灵活性。

反序列化字典中的每个值都是简单类型(即 intstringdouble,...)、IDictionary<string, object>(即 {...})或 ArrayList

我们重写了 DynamicObjectTryGetMember 函数以处理反序列化字典的所有 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 日:更新了源代码。使用 JavaScriptSerializerDynamicJsonObject 转换为字符串。
  • 2012 年 3 月 23 日:更新了源代码。添加 DynamicJsonObjectConverter 以正确序列化 DynamicJsonObject
© . All rights reserved.