.NET 4.0 动态JSON解析器

彭博 发布于 2012/03/20 11:02
阅读 4K+
收藏 2

简介

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

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

背景

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属性,但是程序在编译时不会产生任何错误。而一切在运行时便看起来有所不同了。会抛出出错信息为'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(...)

以下是一个实现了所有上述方法的例子:

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异常。该异常可通过实现一个接受object值的函数来避免。

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方法中提供的dictionary创建新的DynamicJsonObjec。

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

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

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

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

同样我们也会实现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编写

License

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

via:codeproject.com  OSChina原创编译

加载中
返回顶部
顶部