从一个文件或服务器获取数据
要点有哪些?
- 网络上的数据通常使用 JSON 格式化。
- JSON 是基于文本且易读的。
dart:convert
库提供 JSON 支持。- 使用 HttpRequest 来动态加载数据。
Web 应用通常使用 JSON (JavaScript Object Notation)在客户端和服务器之间传递数据。数据可以序列化为一个 JSON 字符串,之后在客户端和服务器之间传递,并在终点重新生成对象。本教程为你展示如何使用 dart:convert 库种的函数来生成和使用 JSON 数据。由于 JSON 数据是典型的动态加载的,本教程也会展示一个 web 应用如何使用一个 HTTP 请求来从一个 HTTP 服务器获取数据。对于 web 应用而言,HTTP 请求是通过运行该应用的浏览器发起的,因此受到浏览器的安全限制。
关于 JSON
JSON 数据格式是易读且易写的,因为它是轻量级和基于文本的。使用 JSON,各种数据类型和简单的数据结构,例如列表和映射可以被序列化且使用字符表示。
试试看,下面的应用——its_all_about_you
,为各种类型的数据显示 JSON 字符串。 点击 run 按钮来运行应用。然后改变 input 元素的值,并检验每种数据类型的 JSON 格式。你可能更喜欢 在 DartPad 中打开应用,以获得更大的空间来显示应用代码和 UI 界面。
// dart code
import 'dart:html';
import 'dart:convert';
// Input fields
InputElement favoriteNumber;
InputElement valueOfPi;
InputElement horoscope;
InputElement favOne;
InputElement favTwo;
InputElement favThree;
RadioButtonInputElement loveChocolate;
RadioButtonInputElement noLoveForChocolate;
// Result fields
TextAreaElement intAsJson;
TextAreaElement doubleAsJson;
TextAreaElement stringAsJson;
TextAreaElement listAsJson;
TextAreaElement boolAsJson;
TextAreaElement mapAsJson;
void main() {
// Set up the input text areas.
favoriteNumber = querySelector('#favoriteNumber');
valueOfPi = querySelector('#valueOfPi');
horoscope = querySelector('#horoscope');
favOne = querySelector('#favOne');
favTwo = querySelector('#favTwo');
favThree = querySelector('#favThree');
loveChocolate = querySelector('#loveChocolate');
noLoveForChocolate = querySelector('#noLoveForChocolate');
// Set up the results text areas
// to display the values as JSON.
intAsJson = querySelector('#intAsJson');
doubleAsJson = querySelector('#doubleAsJson');
boolAsJson = querySelector('#boolAsJson');
stringAsJson = querySelector('#stringAsJson');
listAsJson = querySelector('#listAsJson');
mapAsJson = querySelector('#mapAsJson');
// Set up the listeners.
favoriteNumber.onKeyUp.listen(showJson);
valueOfPi.onKeyUp.listen(showJson);
loveChocolate.onClick.listen(showJson);
noLoveForChocolate.onClick.listen(showJson);
horoscope.onKeyUp.listen(showJson);
favOne.onKeyUp.listen(showJson);
favTwo.onKeyUp.listen(showJson);
favThree.onKeyUp.listen(showJson);
_populateFromJson();
showJson(null);
}
// Pre-fill the form with some default values.
void _populateFromJson() {
String jsonDataAsString = '''
{ "favoriteNumber":73,
"valueOfPi":3.141592,
"chocolate":true,
"horoscope":"Cancer",
"favoriteThings":["monkeys",
"parrots",
"lattes"]
}
''';
Map jsonData = JSON.decode(jsonDataAsString);
favoriteNumber.value = jsonData['favoriteNumber'].toString();
valueOfPi.value = jsonData['valueOfPi'].toString();
horoscope.value = jsonData['horoscope'].toString();
favOne.value = jsonData['favoriteThings'][0];
favTwo.value = jsonData['favoriteThings'][1];
favThree.value = jsonData['favoriteThings'][2];
if (jsonData['chocolate']) {
loveChocolate.checked = true;
} else {
noLoveForChocolate.checked = true;
}
}
// Display all values as JSON.
void showJson(Event e) {
// Grab the data that will be converted to JSON.
num favNum = int.parse(favoriteNumber.value);
num pi = double.parse(valueOfPi.value);
bool chocolate = loveChocolate.checked;
String sign = horoscope.value;
List favoriteThings =
[ favOne.value, favTwo.value, favThree.value ];
Map formData = {
'favoriteNumber': favNum,
'valueOfPi': pi,
'chocolate': chocolate,
'horoscope': sign,
'favoriteThings': favoriteThings
};
// Convert everything to JSON and
// display the results.
intAsJson.text = JSON.encode(favNum);
doubleAsJson.text = JSON.encode(pi);
boolAsJson.text = JSON.encode(chocolate);
stringAsJson.text = JSON.encode(sign);
listAsJson.text = JSON.encode(favoriteThings);
map
// html code
It's All About You
Enter value
Data type
JSON string
Favorite number:
integer
Do you know pi?
double
What's your sign?
String
A few of your favorite things?
List<String>
I love chocolate!
bool
// css code
body {
background-color: #F8F8F8;
font-family: 'Open Sans', sans-serif;
font-size: 14px;
font-weight: normal;
line-height: 1.2em;
margin: 15px;
}
h1, p, td, th, label, table {
color: #333;
}
table {
text-align: left;
border-spacing: 5px 15px
}
label {
font-weight: bold;
}
textarea {
resize: none;
}
.result {
background-color: Ivory;
padding: 5px 5px 5px 5px;
border: 1px solid black;
}
#mapAsJson {
background-color: Ivory;
padding: 5px 5px 5px 5px;
margin-top: 15px;
border: 1px solid black;
width: 500px;
height: 50px;
font-size:14px;
}
table {
text-align: left;
border-spacing: 5px 15px
}
label {
font-weight: bold;
}
textarea {
resize: none;
}
dart:convert
包含两个方便地处理 JSON 字符串的函数:
dart:convert 函数 | 描述 |
---|---|
JSON.decode() | 从包含 JSON 数据的字符串构建 Dart 对象。 |
JSON.encode() | 序列化 Dart 对象为 JSON 字符串。 |
注意: Dart 2 中 JSON 被弃用了,应使用小写的 json,
json.decode()
和json.encode()
。
要使用这些函数,你需要导入dart:convert
到你的 Dart 代码中:
import 'dart:convert';
JSON.encode()
和JSON.decode()
函数可以自动处理如下这些 Dart 类型:
- num
- String
- bool
- null
- List
- Map
序列化数据为 JSON
使用JSON.encode()
函数来序列化一个支持 JSON 的对象。its_all_about_you
例子中的showJson
函数,转换所有的数据为 JSON 字符串。
import 'dart:convert';
...
// Display all values as JSON.
void showJson(Event e) {
// Grab the data that will be converted to JSON.
num favNum = int.parse(favoriteNumber.value);
num pi = double.parse(valueOfPi.value);
bool chocolate = loveChocolate.checked;
String sign = horoscope.value;
List favoriteThings = [ favOne.value, favTwo.value, favThree.value ];
Map formData = {
'favoriteNumber': favNum,
'valueOfPi': pi,
'chocolate': chocolate,
'horoscope': sign,
'favoriteThings': favoriteThings
};
// Convert everything to JSON and display the results.
intAsJson.text = JSON.encode(favNum);
doubleAsJson.text = JSON.encode(pi);
boolAsJson.text = JSON.encode(chocolate);
stringAsJson.text = JSON.encode(sign);
listAsJson.text = JSON.encode(favoriteThings);
mapAsJson.text = JSON.encode(formData);
下面是its_all_about_you
应用使用原始值的 JSON 字符串。
在代码中,布尔和数字类型的值直接使用字面量值,不需要使用引号或其它标记。布尔类型的值不是true
就是false
。使用null
表示空对象。
字符串包含在双引号中。列表使用中括号表示;列表项以逗号分隔。这个例子中的列表包含字符串。映射使用花括号表示;使用冒号分割键-值对,首先是键,之后跟着冒号,最后是值。在本例中,映射中的键是字符串。映射中的值是不同类型的,但它们都是可解析为 JSON 的。
解析 JSON 数据
使用dart:convert
库中的JSON.decode()
函数来从一个 JSON 字符串创建 Dart 对象。its_all_about_you
示例从下面的 JSON 字符串初始化表单的值:
String jsonDataAsString = '''
{ "favoriteNumber":73,
"valueOfPi":3.141592,
"chocolate":true,
"horoscope":"Cancer",
"favoriteThings":["monkeys",
"parrots",
"lattes"]
}
''';
Map jsonData = JSON.decode(jsonDataAsString);
这段代码通过传递一个正确的 JSON 格式的字符串来调用JSON.decode()
函数。注意:Dart 字符串可以使用单引号或双引号来表示字符串。JSON 必须使用双引号。
在本例中,全部 JSON 字符串被硬编码到 Dart 代码中。但它可以通过它自身的表单创建,或从一个静态文件中读取,再或者从一个服务器接收。本章稍后的一个例子将展示如何从一个和应用代码处于相同位置的文件动态获取 JSON 数据。
JSON.decode()
函数从它读取字符串并构建 Dart 对象。在本例中,JSON.decode()
函数基于 JSON 字符串的信息创建一个映射对象。映射包含各种类型的对象:整数、浮点数、布尔值、常规字符串和列表。映射中的所有键都是字符串。
关于 URIs 和 HTTP 请求
要从一个 web 应用发起一个 HTTP GET 请求,你需要为资源提供一个 URI (Uniform Resource Identifier)。一个 URI (Uniform Resource Identifier)是一个资源特殊命名的字符串。一个 URL (Uniform Resource Locator)是一种特定的 URI,它也提供了一个资源的位置。万维网上的 URLs 包含三部分的信息:
- 通信协议
- 服务器主机名
- 资源路径
例如,本页面的 URL 可分解成如下部分:
这个 URL 指定了 HTTP 协议。最基本的,当你在浏览器输入一个 HTTP 地址时,浏览器发送一个 HTTP GET 请求到一个 web 服务器,然后 web 服务器发送一个包含本页内容(或错误信息)的 HTTP 响应。
web 浏览器的大多数 HTTP 请求都是一个简单的获取一个页面内容的 GET 请求。然而,HTTP 协议也允许其它类型的请求,例如从客户端发送数据的 POST 请求。
一个 Dart web 应用运行在一个可以发起 HTTP 请求的浏览器中。这些 HTTP 请求由运行应用的浏览器处理。即使浏览器可以发起到网络上任何地方的 HTTP 请求,一个运行 Dart web 应用的浏览器由于安全限制也只能使用受限的 HTTP 请求。事实上,因为这些限制,web 应用发起的 HTTP 请求主要用于检索和应用处于同样位置的具体的文件的信息。
关于安全限制的注意事项:浏览器关于 HTTP 请求对嵌入的应用执行严格的安全限制。具体地说,一个 web 应用请求的任意资源都必须来自同一源。即,资源必须来自和应用本身相同的协议、主机名和端口。这意味着你的应用不能使用 HTTP 请求通过浏览器从网络上请求任意资源,即使这个请求看起来是无害的(比如一个 GET)。
一些服务器允许通过一个称为 CORS (Cross-origin resource sharing)的途径跨源请求,它使用一个 HTTP 请求头发起请求并接收许可。CORS 是由服务器指定的。
Dart SDK 为系统化的 URI 和 HTTP 请求的发起提供了如下有用的类:
Dart 代码 | 库 | 描述 |
---|---|---|
Uri | (核心库) | 一个表示 URI 的对象。 |
HttpRequest | dart:html | 客户端 HTTP 请求对象。在 web 应用中使用。 |
HttpRequest | dart:io | 服务器端 HTTP 请求对象。不能用在 web 应用中。 |
使用 getString() 函数来加载文件
你的 web 应用可以发起的一个有用的 HTTP 请求是一个为了从和应用同源的服务器上获取数据文件的 GET 请求。下面的例子读取一个名为portmanteaux.json
的数据文件,它包含一个 JSON 格式的单词列表。当你点击按钮时,应用向服务器发起一个 GET 请求,并加载这个文件。
实现注意事项:原始的 portmanteaux 例子加载的是一个和应用处于同一位置的文件:
var path = 'portmanteaux.json';
当我们把代码移动到 DartPad 时,我们没有这个处于同一位置的 JSON 文件,因为 DartPad 最多只支持 3 个文件:一个 Dart 文件,一个 HTML 文件和一个 CSS 文件。变通方案是移动
portmanteaux.json
到 dartlang.org,并配置 dartlang.org 的 CORS headers 来允许来自任何地方的只读访问。
试试!点击运行按钮,然后点击 Get portmanteaux 按钮。
// dart code
import 'dart:async';
import 'dart:convert';
import 'dart:html';
var wordList;
void main() {
querySelector('#getWords').onClick.listen(makeRequest);
wordList = querySelector('#wordList');
}
Future makeRequest(Event e) async {
var path = 'https://www.dartlang.org/f/portmanteaux.json';
try {
processString(await HttpRequest.getString(path));
} catch (e) {
print('Couldn\'t open $path');
handleError(e);
}
}
processString(String jsonString) {
List portmanteaux = JSON.decode(jsonString);
for (int i = 0; i < portmanteaux.length; i++) {
wordList.children.add(new LIElement()..text = portmanteaux[i]);
}
}
handleError(Object error) {
wordList.children.add(new LIElement()..text = 'Request failed.');
}
// html code
Portmanteaux
// css code
body {
background-color: #F8F8F8;
font-family: 'Open Sans', sans-serif;
font-size: 14px;
font-weight: normal;
line-height: 1.2em;
margin: 15px;
}
h1, p, li {
color: #333;
}
#sample_container_id {
width: 100%;
height: 400px;
position: relative;
border: 1px solid #ccc;
background-color: #fff;
}
这个程序使用一个便利的方法——getString()
,它是由 HttpRequest 类提供的用来从服务器请求文件。
getString()
方法使用一个 Future 对象来处理请求。Future 是执行潜在的耗时操作的一种方式,例如异步的 HTTP 请求。如果你还没有遇到过 Futures,你可以在异步编程: Futures中学习更多知识。在那之前,你可以使用上面的代码作为模板,并为processString()
函数体和错误处理提供你自己的代码。
注意:这部分例子使用的
async
和await
关键字。如果你对这些关键字不熟悉,请看 language tour 中的异步支持。
使用一个 HttpRequest 对象加载文件
getString()
方法对于使用 HTTP GET 请求从资源返回加载的字符串时是很好的。对于其它不同的情况,你需要创建一个 HttpRequest 对象,配置它的请求头和其它信息,并使用send()
方法来发起请求。
这一部分,显式地构建一个 HttpRequest 对象重写 portmanteaux 程序代码。
// dart code
import 'dart:html';
import 'dart:convert';
var wordList;
void main() {
querySelector('#getWords').onClick.listen(makeRequest);
wordList = querySelector('#wordList');
}
void makeRequest(Event e) {
var path = 'https://www.dartlang.org/f/portmanteaux.json';
var httpRequest = new HttpRequest();
httpRequest
..open('GET', path)
..onLoadEnd.listen((e) => requestComplete(httpRequest))
..send('');
}
requestComplete(HttpRequest request) {
if (request.status == 200) {
List portmanteaux = JSON.decode(request.responseText);
for (int i = 0; i < portmanteaux.length; i++) {
wordList.children.add(new LIElement()..text = portmanteaux[i]);
}
} else {
wordList.children.add(new LIElement()
..text = 'Request failed, status=${request.status}');
}
}
// html code
Portmanteaux
配置 HttpRequest 对象
按钮的鼠标点击处理函数创建一个 HttpRequest 对象,使用一个 URI 和回调函数配置它,然后发送请求。让我们看一看 Dart 代码:
void makeRequest(Event e) {
var path = 'https://www.dartlang.org/f/portmanteaux.json';
var httpRequest = new HttpRequest();
httpRequest
..open('GET', path)
..onLoadEnd.listen((e) => requestComplete(httpRequest))
..send('');
}
发送请求
send()
方法向服务器发送请求。
httpRequest.send('');
因为这个例子中的请求是一个简单的 GET 请求,这段代码可以发送一个空字符串。对于其它类型的请求,比如 POST 请求,这个字符串可以包含更多细节或相关的数据。你也可以使用setRequestHeader()
方法通过设置各种各样的 header 参数,配置 HttpRequest 对象。
处理响应
要处理来自请求的响应,你需要在调用send()
之前设置一个回调函数。我们的例子为onLoadEnd
事件设置了一个一行的回调函数,它在返回值中调用requestComplete()
。当请求完成时,不管成功还是失败都会调用这个回调函数。
我们例子中的回调函数——requestComplete()
,检查请求的状态码。如果请求的状态码是 200,表示文件找到并加载成功,请求文件——portmanteaux.json
的内容,在 HttpRequest 对象的responseText
属性中返回。使用 dart:convert 库的JSON.decode()
函数,代码很容易把 JSON 格式的单词列表转换成一个 Dart 的字符串列表,为列表的每个元素创建一个新的 LIElement,并且把它们添加到页面上的
- 元素中。
从 JSON 构建 UI
portmanteaux 例子中的数据文件——portmanteaux.json
,包含一个 JSON 格式的字符串列表。
[
"portmanteau", "fantabulous", "spork", "smog",
"spanglish", "gerrymander", "turducken", "stagflation",
"bromance", "freeware", "oxbridge", "palimony", "netiquette",
"brunch", "blog", "chortle", "Hassenpfeffer", "Schnitzelbank"
]
根据请求,服务器从文件中读取这些数据,并把它当作一个单独的字符串发送给客户端程序。客户端接收这个 JSON 字符串,并使用JSON.decode()
通过这个 JSON 字符串创建指定的字符串对象。