IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

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

老赵点滴 2011-02-10 22:34:08 累计浏览 2,357 次
本机暂存

    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. 等了十年的 Go 链式管道,终于来了:seq 让你像写 Scala 一样写 Go (2026-06-25 18:38:18)
  2. Go 实验特性详解 (2026-06-21 10:05:27)
  3. amd64 微架构级别对 Go 程序性能提升多少? (2026-06-21 09:38:49)

查看更多 后端 文章 →

建议继续学习

  1. JSON和JSONP的区别 (累计阅读 8,467)
  2. 代理的加密部分 (累计阅读 8,417)
  3. XML和JSON (累计阅读 7,726)
  4. 关于”为什么京东今天还在用.net架构?”的乱想 (累计阅读 7,201)
  5. 干嘛不去掉“I”和“Impl”? (累计阅读 6,751)
  6. 玩转Protocol Buffers (累计阅读 6,615)
  7. 如何编写一个JSON解析器 (累计阅读 6,361)
  8. 国内团购网前端严重安全漏洞– 以满座网为案例分析 (累计阅读 5,874)
  9. 理解JSON:3分钟课程 (累计阅读 5,792)
  10. .NET 还是 Java? (累计阅读 5,657)