在现代编程中,JSON(JavaScript Object Notation)已经成为一种标准的代码数据交换格式。几乎所有的编程语言都以某种形式支持它,通常是以关联数组的形式出现,有时也会以模板和泛型的形式存在。JSON本身是一种交换格式,它非常适合表示嵌套的关联数组和列表中的简单数据,因此可以构建整个嵌套的“元组”结构,并将其表示为JSON。
JSON的主要用途是作为存储库和传输格式,但在这里更感兴趣的是其结构中的元组方面。JSON的主要限制在于它的简单性,它不支持很多数据类型。最明显的是,它缺少纯整数类型,也没有直接有效嵌入二进制数据的方法。此外,它也比需要的更冗长,例如,字段名称需要用引号包围。
显然,不是在这里重新发明一种新的传输格式,但只要能够在需要时输出正确的JSON,就没有什么坏处可以给JSON语法添加一些语法糖。因此,不需要字段名称周围的引号。可以扩展语法来隐式支持整数值,并且可以放宽对字符串转义和引用的一些限制。
对于其他任何事情,比如二进制字段,可以选择不将其渲染为JSON(仅限本地),或者可以简单地返回字段作为JSON兼容值的最佳努力近似。可能还想要解析片段——比如一个字段:“值”对,例如,所以不希望JSON的限制是文档必须由一个对象 { } 根。
重要的是——JSON数据应该能够立即与代码一起工作。Struples是JSON语法的超集——基本上是一个扩展的JSON语法,大约80%是语法糖,20%是没有JSON对应扩展的。
需要一种方式来读取和写入文本格式,以及一种方式来查询和操作结构,以便能够正确地使用。Struples使用.NET IDictionary
有一个拉式解析器,StrupleTextReader,类似于XmlTextReader,用于高效地从流源读取元组。解析器包含一些补救查询方法,用于通过字段选择一个项目,以及基本的读取、跳过和解析方法。
任何返回的Struple都可以转换为字符串,或者写入流或追加到StringBuilder,因此无论在什么情况下,写入都和解析一样高效。如果不想,永远不必处理整个文档加载到内存中,但可以。因此,只要适当使用,这段代码应该能够很好地扩展。
说到性能,这里有一个重要的考虑——格式良好性。Struple解析器以速度和批量数据处理为交易。因为几乎所有的JSON数据都是机器生成的,可以做出一个善意的假设,即数据是格式良好的。具体来说,性能的好处是值得风险的。Struples代码在解析和查询的许多阶段都假设数据的格式良好性。结果是提高了性能,缺点是在面对无效的JSON数据时的脆弱性。这几乎总是值得的。
使用代码的简要示例:
// 声明一个简单的员工对象,包含嵌入的二进制数据
string struple =
@"
{id: 1,firstname: 'Honey',lastname: 'Crisis', photo: ^iVBORw0KGgoAAAANS... }
";
// 解析为DOM。由于Parse可以返回文档片段,它返回对象。
// 它可能返回KeyValuePair<string,object>在字段片段的情况下
// 一个IList<object>在“数组”的情况下
// 或者是一个int, double, bigint, long, boolean或null(object)或字符串
// 任何 { } 元组“对象”将返回一个Struple (IDictionary<string,object>)
var doc = Struple.Parse(struple) as Struple;
// 通过索引器读取字段
var photo = doc["photo"] as byte[];
var id = (int)doc["id"];
var name = string.Concat(doc["firstname"]," ", doc["lastname"]);
// 使用动态调用站点读取
dynamic employee = doc;
photo = employee.photo;
id = employee.id;
name = employee.firstname + " " + employee.lastname;
// 演示设置一些字段(也可以使用DOM索引器完成)
employee.lastname = "The Monster";
employee.photo = null;
// 清除这个,以免淹没控制台
// 将结果以JSON形式漂亮地打印到控制台
StrupleUtility.WriteJsonTo(employee, Console.Out, "");
Console.WriteLine();
// 以本地形式漂亮地打印Struple
StrupleUtility.WriteTo(employee, Console.Out, "");
Console.WriteLine();
// 从流中解析——可以是URL
// Struple.ReadFrom()更容易——这只是长手
using (var tr = new StrupleTextReader(new StreamReader(@"
..\..\..\Burn Notice.2919.tv.json")))
{
// 解析为DOM
doc = tr.ParseSubtree() as Struple;
// 使用StrupleUtility.Format写出一些数据
Console.WriteLine("Using StrupleUtility.Format");
Console.WriteLine(StrupleUtility.Format("Show: {name} ({homepage}), Last episode name: {last_episode_to_air.name}", (Struple)doc));
// 使用string.Format/dynamic写出一些数据
dynamic show = doc;
Console.WriteLine("Using string.Format with dynamic");
Console.WriteLine(string.Format("Show: {0} ({1}), Last episode name: {2}", show.name, show.homepage, show.last_episode_to_air.name));
// 使用Get扩展方法写出一些数据
Console.WriteLine("Using string.Format with Get");
Console.WriteLine(string.Format("Show: {0} ({1}), Last episode name: {2}", doc.Get("name"), doc.Get("homepage"), doc.Get("last_episode_to_air").Get("name")));
// 与StrupleTextReader做一些性能测试
Console.WriteLine();
ReadSkipParsePerf();
}