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的源代码和简单示例,修改修改,尝试尝试。我认为它还是相当实用的。
觉得文章有用?立即: 和朋友一起 共学习 共进步!
建议继续学习:
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习