七、以类型安全的方式使用 JSON
在本章中,我们基于第 1 章,在客户端上读写 JSON,展示了如何使用 c#、Java 和 TypeScript 在应用中使用 JSON 强类型。 你会发现以下食谱:
- 如何使用 Json 反序列化对象。 净
- 如何使用 Json 处理日期和时间对象。 净
- 如何使用 Java 的 gson 来反序列化一个对象
- 如何在 Node.js 中使用 TypeScript
- 如何使用 TypeScript 注释简单的类型
- 如何使用 TypeScript 声明接口
- 如何使用 TypeScript 声明带有接口的类
- 使用 json2ts 从 JSON 生成 TypeScript 接口
简介
虽然有些人说强类型是为弱智准备的,但事实是,编程语言中的强类型可以帮助您避免错误的整个类,在这些类中,您错误地假设一个类型的对象实际上是另一个类型。 c#和 Java 等语言提供强类型正是出于这个原因。
幸运的是,c#和 Java 的 JSON 序列化器支持强类型,当您确定了对象表示并只是想将 JSON 映射到已经定义的类的实例时,这种方法特别方便。 在第 1 章,在客户端上读写 JSON,您看到了如何从 c#或 Java 类转换为 JSON,以及如何将 JSON 转换回非类型化对象; 在本章中,我们使用 Json。 NET 用于 c#, gson 用于 Java,可以将 JSON 转换为应用中定义的类的实例。
最后,我们来看看 TypeScript,它是 JavaScript 的一个扩展,提供编译时类型检查,编译成普通的 JavaScript,供 Node.js 和浏览器使用。 我们将学习如何为 Node.js 安装 TypeScript 编译器,如何使用 TypeScript 注释类型和接口,以及如何使用 Timmy Kokke 的网页从 JSON 对象自动生成 TypeScript 接口。
如何使用 Json 反序列化一个对象。 净
在这个食谱中,我们向您展示如何使用 Newtonsoft 的 Json。 NET 将 JSON 反序列化为一个类的实例对象。 我们将使用 Json。 ,在客户端上读和写 JSON,因为尽管它与现有的。NET JSON 序列化器一起工作,我想让你知道关于 JSON 的其他事情。 NET,我们将在接下来的两个菜谱中讨论它。
准备好
首先,您需要确保有一个对 Json 的引用。 。NET。 最简单的方法是使用 NuGet; 启动 NuGet,搜索 Json。 然后点击安装,如下图所示:
你还将需要引用Newonsoft.Json
命名空间在任何文件中,需要这些类的using
指令在你的文件顶部:
usingNewtonsoft.Json;
How to do it…
下面的例子提供了一个简单类的实现,将一个 JSON 字符串转换为该类的一个实例,然后将实例转换回 JSON:
using System;
usingNewtonsoft.Json;
namespaceJSONExample
{
public class Record
{
public string call;
public double lat;
public double lng;
}
class Program
{
static void Main(string[] args)
{
String json = @"{ 'call': 'kf6gpe-9',
'lat': 21.9749, 'lng': 159.3686 }";
var result = JsonConvert.DeserializeObject<Record>(
json, newJsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Error
});
Console.Write(JsonConvert.SerializeObject(result));
return;
}
}
}
How it works…
在中,为了以一种类型安全的方式对 JSON 进行反序列化,我们需要一个具有与 JSON 相同字段的类。 在前几行中定义的Record
类就是这样做的,它定义了call
、lat
和lng
的字段。
Newtonsoft.Json
命名空间为JsonConvert
类提供了SerializeObject
和DeserializeObject
静态方法。 DeserializeObject
是一个通用方法,接受应该返回的对象类型作为类型参数,作为要解析的 JSON 参数,以及一个指示 JSON 解析选项的可选参数。 我们将MissingMemberHandling
属性作为设置传递,用枚举Error
的值指示在字段丢失的情况下,解析器应该抛出异常。 解析类之后,我们将其再次转换为 JSON,并将生成的 JSON 写入控制台。
There's more…
如果您跳过了MissingMember
选项或传递Ignore
(默认),那么您的 JSON 和类中的字段名可能会不匹配,这可能不是您想要的类型安全转换。 您还可以传递值为Include
或Ignore
的NullValueHandling
字段。 如果Include
,包含空值字段; 如果Ignore
,Null 值的字段将被忽略。
参见
Json 的完整文档 网址:http://www.newtonsoft.com/json/help/html/Introduction.htm。
使用.NET 序列化器支持 JSON 也可以实现类型安全的反序列化; 语法是相似的。 例如,请参阅 JavaScriptSerializer 类的文档https://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer(v=vs.110).aspx。
如何使用 Json 处理日期和时间对象 净
JSON 中的日期对人们来说是有问题的,因为 JavaScript 的日期是以毫秒为单位的,这通常是不可读的。 不同的 JSON 解析器处理方法不同; Json。 NET 有一个很好的IsoDateTimeConverter
,它将日期和时间格式化为 ISO 格式,使其易于在除 JavaScript 以外的平台上调试或解析。 通过创建新的转换器对象,并使用转换器对象将一种值类型转换为另一种值类型,您也可以扩展此方法,将任何类型的格式化数据转换为 JSON 属性。
How to do it…
简单地包括一个新的IsoDateTimeConverter
对象当你调用JsonConvert.Serialize
,像这样:
string json = JsonConvert.SerializeObject(p,
newIsoDateTimeConverter());
How it works…
这会导致序列化器调用任何日期和时间对象实例的IsoDateTimeConverter
实例,在 JSON 中返回这样的 ISO 字符串:
2015-07-29T08:00:00
There's more…
注意,这可以通过 Json 进行解析。 NET,而不是 JavaScript; 在 JavaScript 中,你会想要使用这样的函数:
Function isoDateReviver(value) {
if (typeof value === 'string') {
var a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(?:([\+-])(\d{2})\:(\d{2}))?Z?$/
.exec(value);
if (a) {
var utcMilliseconds = Date.UTC(+a[1],
+a[2] - 1,
+a[3],
+a[4],
+a[5],
+a[6]);
return new Date(utcMilliseconds);
}
}
return value;
}
第三行上的非常复杂的正则表达式匹配 ISO 格式的日期,提取每个字段。 如果正则表达式找到匹配,它提取每个日期字段,然后Date
类的 UTC 方法使用这些字段来创建一个新的日期。
提示
请注意,整个正则表达式(在/
字符之间的所有字符)应该在一行中,不能有空格。 然而,对于这个页面来说,它有点长!
参见
关于 Json 的更多信息。 NET 句柄的日期和时间,见文档和示例在http://www.newtonsoft.com/json/help/html/SerializeDateFormatHandling.htm。
如何使用 Java 的 gson 来反序列化对象
比如 Json。 NET, gson 提供了一种方法来指定要反序列化 JSON 对象的目标类。 事实上,这与你在在 Java中读写 JSON、第 1 章、客户端中使用的方法相同。
准备好
您需要在应用中包含 gson JAR 文件,就像您将其他任何外部 API 一样。
How to do it…
使用的方法与使用fromJson
的 gson 解析类型不安全 JSON 时使用的方法相同,只是将类对象作为第二个参数传递给 gson,如下所示:
// Assuming we have a class Record that looks like this:
/*
class Record {
private String call;
private float lat;
private float lng;
// public API would access these fields
}
*/
Gson gson = new com.google.gson.Gson();
String json = "{ \"call\": \"kf6gpe-9\",
\"lat\": 21.9749, \"lng\": 159.3686 }";
Record result = gson.fromJson(json, Record.class);
How it works…
fromGson
方法总是接受一个 Java 类; 在第 1 章,客户端上读写 JSON中,我们反序列化的类是JsonElement
,它处理 JSON 的一般动态特性。 在本例中,我们直接转换为应用可以使用的普通旧 Java 对象,而不需要使用 gson 提供的JsonElement
的解引用和类型转换接口。
There's more…
gson 库还可以处理嵌套类型和数组。 您还可以通过声明字段transient
来隐藏字段,使其不被序列化或反序列化,这是有意义的,因为瞬态字段不是序列化的。
参见
gson 的文档及其对类的反序列化实例的支持在https://sites.google.com/site/gson/gson-user-guide#TOC-Object-Examples。
如何在 Node.js 中使用 TypeScript
在 Visual Studio 中使用 TypeScript 很容易; 对于 Visual Studio 2013 Update 2 之后的任何版本,它只是 Visual Studio 安装的的一部分。 为 Node.js 获取 TypeScript 编译器几乎同样容易——这是一个npm install
之事。
How to do it…
在路径中有 npm 的命令行中,运行以下命令:
npm install –g typescript
npm 选项–g
告诉 npm 全局安装 TypeScript 编译器,这样你编写的每个 Node.js 应用都可以使用它。 一旦你运行了它,npm 就会为你的平台下载并安装 TypeScript 编译器二进制文件。
There's more…
一旦你运行这个命令来安装编译器,你就可以在命令行中使用 TypeScript 编译器 tsc 了。 用 tsc 编译文件就像编写源代码并保存在以.ts
扩展名结尾的文件中一样简单,然后在上面运行 tsc。 例如,给定以下 TypeScript 文件hello.ts
:
function greeter(person: string) {
return "Hello, " + person;
}
var user: string = "Ray";
console.log(greeter(user));
在命令行运行tschello.ts
创建以下 JavaScript:
function greeter(person) {
return "Hello, " + person;
}
var user = "Ray";
console.log(greeter(user));
试一试!
我们将在下一节看到,greeter
的函数声明包含一个 TypeScript 注释; 它声明参数人为string
。 在hello.ts
的底部添加以下一行:
console.log(greeter(2));
现在,再次运行tschello.ts
命令; 你会得到这样一个错误:
C:\Users\rarischp\Documents\node.js\typescript\hello.ts(8,13): error TS2082: Supplied parameters do not match any signature of call target:
Could not apply type 'string' to argument 1 which is of type 'number'.
C:\Users\rarischp\Documents\node.js\typescript\hello.ts(8,13): error TS2087: Could not select overload for 'call' expression.
这个错误表明我试图用一个错误类型的值调用greeter
,传递一个greeter
期望字符串的数字。 在下一篇文章中,我们将介绍 TypeScript 对简单类型的类型注释支持。
参见
TypeScript 首页有教程和参考文档,位于http://www.typescriptlang.org/。
如何使用 TypeScript 注释简单的类型
TypeScript 中的类型注释是附加在冒号后面的变量或函数的简单装饰器。 它支持与 JavaScript 中相同的基本类型,并支持声明接口和类,这一点我们将在后面讨论。
How to do it…
下面是一些简单的变量声明和两个函数声明的例子:
function greeter(person: string): string {
return "Hello, " + person;
}
function circumference(radius: number) : number {
var pi: number = 3.141592654;
return 2 * pi * radius;
}
var user: string = "Ray";
console.log(greeter(user));
console.log("You need " +
circumference(2) +
" meters of fence for your dog.");
这个示例展示了如何注释函数和变量。
How it works…
变量——无论是独立的变量还是作为函数参数的变量——都使用冒号和类型进行修饰。 例如,第一个函数greeter
接受一个参数 person,它必须是一个字符串。 第二个函数,circumference
,接受一个半径,它必须是一个数字,并在它的作用域中声明了一个单独的变量pi
,它必须是一个数字,其值为3.141592654
。
您可以像在 JavaScript 中那样以正常的方式声明函数,然后在函数名之后添加类型注释,同样使用冒号和类型。 因此,greeter
返回一个字符串,circumference
返回一个数字。
There's more…
TypeScript 定义了以下基本类型装饰器,它们映射到它们的底层 JavaScript 类型:
array
:此为复合类型。 例如,你可以这样写一个字符串列表:boolean
:这个类型装饰器可以包含true
和false
值。number
:这个类型装饰器就像 JavaScript 本身,可以是任何浮点数。string
:这个类型装饰器是一个字符串。-
enum
:一个枚举,用enum
关键字写,如下:enumColor { Red = 1, Green, Blue }; var c : Color = Color.Blue;
-
any
:该类型表示该变量可以是任何类型。 void
:该类型表示该值没有类型。 您将使用void
来指示一个不返回任何内容的函数。
参见
关于 TypeScript 类型的列表,请参见 TypeScript 手册http://www.typescriptlang.org/Handbook。
如何使用 TypeScript 声明接口
一个接口定义如何行为,而不定义实现。 在 TypeScript 中,接口通过描述它的字段来命名复杂类型。 这就是所谓的结构子类型。
How to do it…
声明接口有点像声明结构或类; 在接口中定义字段,每个字段都有自己的类型,如下所示:
interface Record {
call: string;
lat: number;
lng: number;
}
Function printLocation(r: Record) {
console.log(r.call + ': ' + r.lat + ', ' + r.lng);
}
var myObj = {call: 'kf6gpe-7', lat: 21.9749, lng: 159.3686};
printLocation(myObj);
How it works…
TypeScript 中的interface
关键字定义了一个接口; 正如我已经提到的,接口由它声明的字段及其类型组成。 在这个清单中,我定义了一个普通的 JavaScript 对象myObj
,然后调用之前定义的函数printLocation
,该函数的参数为Record
。 当使用myObj
调用printLocation
时,TypeScript 编译器会检查字段并输入每个字段,只有当对象与接口匹配时才允许调用printLocation
。
There's more…
小心! TypeScript 只能提供编译类型检查。 你认为下面的代码是做什么的?
interface Record {
call: string;
lat: number;
lng: number;
}
Function printLocation(r: Record) {
console.log(r.call + ': ' + r.lat + ', ' + r.lng);
}
var myObj = {call: 'kf6gpe-7', lat: 21.9749, lng: 159.3686};
printLocation(myObj);
var json = '{"call":"kf6gpe-7","lat":21.9749}';
var myOtherObj = JSON.parse(json);
printLocation(myOtherObj);
首先,使用tsc
可以很好地编译。 当你用 node 运行它时,你会看到以下内容:
kf6gpe-7: 21.9749, 159.3686
kf6gpe-7: 21.9749, undefined
发生了什么事? TypeScript 编译器不会在你的代码中添加运行时类型检查,所以你不能把一个接口强加给一个运行时创建的非字面量对象。 在这个例子中,由于 JSON 中缺少lng
字段,函数无法打印它,而是打印值undefined
。
然而,这并不意味着你不应该在 JSON 中使用 TypeScript。 类型注释为代码的所有读者服务,无论是编译器还是人。 您可以使用类型注释来表明您作为开发人员的意图,代码的读者可以更好地理解您所编写的代码的设计和限制。
参见
有关接口的更多信息,请参阅 TypeScript 文档http://www.typescriptlang.org/Handbook#interfaces。
如何使用 TypeScript 声明带有接口的类
接口允许您指定行为而不指定实现; 类允许您将实现细节封装在接口后面。 TypeScript 类可以封装字段或方法,就像其他语言中的类一样。
How to do it…
下面是我们的 Record 结构的一个例子,这次是一个带有接口的类:
class RecordInterface {
call: string;
lat: number;
lng: number;
constructor(c: string, la: number, lo: number) {}
printLocation() {}
}
class Record implements RecordInterface {
call: string;
lat: number;
lng: number;
constructor(c: string, la: number, lo: number) {
this.call = c;
this.lat = la;
this.lng = lo;
}
printLocation() {
console.log(this.call + ': ' + this.lat + ', ' + this.lng);
}
}
var myObj : Record = new Record('kf6gpe-7', 21.9749, 159.3686);
myObj.printLocation();
How it works…
关键字interface
同样定义了一个接口,如上一节所示。 你以前没见过的class
关键字实现了一个类; 可选关键字implements
表示该类实现接口RecordInterface
。
注意:实现接口的类必须具有接口规定的所有相同的字段和方法; 否则,它就不符合接口的要求。 因此,我们的Record
类包含了call
、lat
和lng
的字段,这些字段的类型与接口中的相同,还有方法构造函数和printLocation
。
构造函数方法是一个特殊的方法,当你使用new
创建一个类的新实例时调用。 注意,对于类,与普通对象不同,创建类的正确方法是使用构造函数,而不是仅仅将它们构建为字段和值的集合。 我们在清单的第二行到最后一行执行此操作,将构造函数参数作为函数参数传递给类构造函数。
参见
使用类可以做的事情还有很多,包括定义继承和创建公共和私有字段和方法。 有关 TypeScript 中类的更多信息,请参见http://www.typescriptlang.org/Handbook#classes文档中的。
使用 json2ts 从 JSON 生成 TypeScript 接口
这个最后的食谱更像是一个提示而不是食谱; 如果你使用另一种编程语言或手工开发了一些 JSON,你可以通过使用 Timmy Kokke 的 json2ts 网站,轻松地为对象创建一个包含 JSON 的 TypeScript 接口。
How to do it…
只需点击http://json2ts.com,将 JSON 粘贴到出现的框中,然后点击生成 TypeScript 按钮。 你会得到第二个文本框的奖励,它会显示 TypeScript 接口的定义,你可以将它保存为自己的文件,并包含在你的 TypeScript 应用中。
How it works…
下图是一个简单的示例:
你可以把这个 typescript 文件保存为自己的文件,也就是一个definition
文件,后缀是.d.ts
,然后在你的 typescript 中使用import
关键字包含该模块,如下所示:
import module = require('module');
版权属于:月萌API www.moonapi.com,转载请注明出处