技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 算法 --> JsonMe - 合约与类型分离的轻量级JSON映射类库

JsonMe - 合约与类型分离的轻量级JSON映射类库

浏览:1756次  出处信息

    JSON全称为JavaScript Object Notation,原本作为JavaScript语言中用于表示对象结构的文本形式。不过目前JSON成功地脱离了JavaScript语言,它已经成为一种运用十分广泛的数据交换格式。从表面看来,目前用于某个对象与JSON格式之间相互转化的解决方案已经有了许多种,例如在.NET平台上,我们可以使用ASP.NET AJAX中引入的JavaScriptSerializer,WCF中引入的DataContractJsonSerializer,亦或是Json.NET。但是,最近我忽然发现这些类库都无法满足我的要求,因此,我今天花了一点时间,写了一个非常简单的对象与JSON格式相互转化的类库,是为JsonMe

现有解决方案的不足

    您可能会感到疑惑,难道现有的解决方案都不够好吗?又搞一个新的实现出来,这不是重复造轮子嘛。但事实上,它们在我眼里,都有一些难以逾越的障碍。就拿JavaScriptSerializer来说,它使用起来十分简单,配合C#的匿名对象特性,输出一个JSON格式可谓无比直接:

var value = new
{
    hello = "world",
    array = new object[] { 1, 2, 3, "jeffz" }
};

var json = new JavaScriptSerializer().Serialize(value);

    JavaScriptSerializer也支持JSON格式与某种类型的对象相互转化,甚至可以加上ScriptIgnoreAttribute标记来忽略某个属性,例如:

public class Post
{
    public string Title { get; set; }

    [ScriptIgnore]
    public string Content { get; set; }

    public DateTime CreateTime { get; set; }
}

    如果我想要改变某个属性在JSON中的字段名呢?JavaScriptSerializer应该也做得到,但我现在我一时想不起来了,也懒得去查。我知道DataContractJsonSerializer一定支持,事实上它是.NET中用于代替JavaScriptSerializer的JSON序列化解决方案,为此如今的JavaScriptSerializer已经标记了ObsolateAttribute,编译时您应该可以看到warning。

    那么我再提一个要求:在序列化某个字段的时候,对它的值进行一个简单的转化。例如,我希望将DateTime对象在JSON中表现为字符串,这对于JavaScriptSerializer和DataContractJsonSerializer来说似乎就做不到了,至少会十分麻烦。但是这对于Json.NET来说不是个问题,相比于前两者来说,Json.NET可谓是JSON解决方案中的Word和Excel,经过了许多版本的积极开发,如今的Json.NET已经实现了大量的功能,几乎在每一处都提供了扩展点。我对Json.NET的了解不多,不过在简单浏览代码以后,我发现它的复杂度已经能和如今的ASP.NET MVC比肩了,是不是显得有些可怕?我也这么觉得。

    但事实上,让我重新写一个JsonMe的关键,是因为我希望同一种类型能够与不同的JSON格式相互转化。试想,我有一个User类型,但是我却有两个不同格式的JSON数据源。或者说,相同的User对象,需要根据环境得到两种不同格式的JSON输出。这样JavaScriptSerializer或DataContractJsonSerializer就无法满足我的要求了,因为它们使用自定义特性来控制JSON的格式,但一个类型只能应用一种JSON策略,这又让我如何是好?

    当然,这点对于Json.NET应该不是问题,但是它实在太复杂了,如果我能把它研究透彻并加以扩展,还不如自己重新去写一个简单的类库呢。于是,一个秋高气爽的周日下午,JsonMe就此诞生了。

JsonMe使用方式

    为了实现以上的要求,在JsonMe中我将JSON转化的“合约”与类型本身分离。我很喜欢这样的策略,正如EasyMongo那样,除了一个类型可以对应多种映射策略之外,我还能将一种类型与它的映射策略解耦。例如,现在有一个User对象:

public class User
{
    public string UserName { get; set; }
    public int Age { get; set; }
    public bool IsAdult { get { return this.Age >= 18; } }
}

    在进行序列化之前,需要先定义这样的“合约”(即映射策略):

var userContract = new JsonContract();
userContract.SimpleProperty(u => u.UserName).Name("Name");
userContract.SimpleProperty(u => u.Age);

    User类型中定义了三个属性,其中我们只采纳了两个:Age和UserName(并改名为Name)。利用这个“合约”我们可以进行JSON转化:

var user = new User { UserName = "Tom", Age = 20 };
var jsonUser = JsonSerializer.SerializeObject(user, userContract);
Console.WriteLine(jsonUser);

    这将会输出(已经过手动格式化):

{
    "Name" : "Tom",
    "Age" : 20
}

    同样,我们可以为某个属性运用转化规则,只要提供一个IJsonConverter对象即可:

public class Post
{
    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime CreateTime { get; set; }
}

public class DataTimeConverter : IJsonConverter
{
    public object ToJsonValue(Type type, object value)
    {
        return ((DateTime)value).ToString("R");
    }

    public object FromJsonValue(Type type, object value)
    {
        return DateTime.ParseExact((string)value, "R", null);
    }
}

    定义合约:

var postContract = new JsonContract();
postContract.SimpleProperty(p => p.Title);
postContract.SimpleProperty(p => p.CreateTime).Converter(new DataTimeConverter());

    在这里,DataTimeConverter会将CreateTime属性的时间日期转化为字符串以后再输出。例如:

var post = new Post { Title = "Good day today.", CreateTime = DateTime.Now };
Console.WriteLine(JsonSerializer.SerializeObject(post, postContract));
Console.WriteLine();

    便会得到:

{
    "Title" : "Good day today.",
    "CreateTime" : "Sun, 10 Oct 2010 23:12:53 GMT"
}

    我们可以做得还有更多,例如这里有一个包含复杂属性的Category对象:

public class Category
{
    public string Name { get; set; }
    public User Author { get; set; }
    public List Posts { get; set; }
}

    我们在定义Category的合约时,则可以用到之前的合约:

var categoryContract = new JsonContract();
categoryContract.SimpleProperty(p => p.Name);
categoryContract.ComplexProperty(p => p.Author).Contract(userContract);
categoryContract.ArrayProperty(p => p.Posts).ElementContract(postContract);

    那么下面这段代码:

var category = new Category
{
    Name = "Default",
    Author = new User { UserName = "Jerry", Age = 15 },
    Posts = new List
    {
        new Post { Title = "Post 1", CreateTime = new DateTime(2010, 1, 1) },
        new Post { Title = "Post 2", CreateTime = new DateTime(2010, 2, 1) },
        new Post { Title = "Post 3", CreateTime = new DateTime(2010, 3, 1) }
    }
};
var jsonCategory = JsonSerializer.SerializeObject(category, s_categoryContract);
Console.WriteLine(jsonCategory);

    将会输出:

{
    "Name" : "Default",
    "Author" :
    {
        "Name" : "Jerry",
        "Age" : 15
    },
    "Posts" :
    [
        {
            "Title" : "Post 1",
            "CreateTime" : "Fri, 01 Jan 2010 00:00:00 GMT"
        },
        {
            "Title" : "Post 2",
            "CreateTime" : "Mon, 01 Feb 2010 00:00:00 GMT"
        },
        {
            "Title" : "Post 3",
            "CreateTime" : "Mon, 01 Mar 2010 00:00:00 GMT"
        }
    ]
}

    当然,您同样可以定义一个匿名对象作为JSON输出:

var value = new { v = JsonSerializer.SerializeObject(category, categoryContract) };
Console.WriteLine(JsonSerializer.Serialize(value));

    是不是很简单?

更多讨论

    以上代码只是演示了序列化成JSON的功能,但是您应该也可以了解反序列化的使用方式。目前JsonMe的功能就只有这么多,可以说非常简单,但是我认为已经基本够用,甚至在大部分情况下完整代替JavaScriptSerializer和DataContractJsonSerialzier是没有什么问题的。

    开发JsonMe恰好花了我五个小时(下午2点到7点),只有几百行代码(当然还很粗糙),大部分还是用于合约配置的“骨架”,真正进行对象属性的赋值和转化的代码只有一百多行。JsonMe能够如此简约的原因,是因为站在JavaScriptSerializer的肩膀上。说实话,JavaScriptSerializer其实提供了一个很好的基础,因为它可以将一段JSON字符串转化为Dictionary与object数组间的嵌套,这正是JSON格式的本质。当然,为了实现简单,我在JsonMe中创建了JsonObject和JsonArray两个对象,分别继承Dictionary和List,它们便作为JSON结构的表现形式。

    不过我也意识到,JavaScriptSerializer可能并不是一个合适的选择,因为这会让我们依赖System.Web.Extensions.dll。事实上在.NET平台上有一个更独立,更简单的JSON结构实现,那就是Silverlight中的System.Json.dll。只可惜我们只能用它开发Silverlight程序。我打算在合适的时候,将mono中的System.Json.dll实现移植到.NET 3.5中,这样JsonMe就可以摆脱对System.Web.Extensions.dll的依赖,并摆脱自定义的JsonObject和JsonArray,可以直接使用System.Json里的结构(已完成)。更重要的是,这可以让JsonMe作用在Silverlight,甚至是基于MonoTouch的iOS开发中(很有可能还包括未来的MonoDroid)。

    如果您感兴趣的话,也不妨获取JsonMe的源代码和简单示例,修改修改,尝试尝试。我认为它还是相当实用的。

建议继续学习:

  1. JSON和JSONP的区别    (阅读:7524)
  2. XML和JSON    (阅读:6540)
  3. 如何编写一个JSON解析器    (阅读:5295)
  4. 理解JSON:3分钟课程    (阅读:4742)
  5. 用 JavaScript 对 JSON 进行模式匹配 (Part 1 - 设计)    (阅读:4350)
  6. 前端模板引擎    (阅读:3749)
  7. 用 JavaScript 对 JSON 进行模式匹配 (Part 2 - 实现)    (阅读:3689)
  8. Ajax和WEB服务数据格式:JSON JSONP    (阅读:3403)
  9. [python]定制JSON中的浮点数格式    (阅读:2976)
  10. JSON对象和字符串之间的相互转换    (阅读:2899)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1