动态获取数据

从一个文件或服务器获取数据

要点有哪些?
  • 网络上的数据通常使用 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<String> 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

<h1>It's All About You</h1>

    <table>
      <thead>
      <tr>        <th> </th>
        <th>Enter value</th>
        <th>Data type</th>
        <th>JSON string</th>
      </tr>
      </thead>

      <tbody>
      <tr>
        <td align="right">Favorite number:</td>
        <td><input type="text" id="favoriteNumber"></td>
        <td>integer</td>
        <td><textarea class="result" id="intAsJson" readonly></textarea></td>
      </tr>

      <tr>
        <td align="right">Do you know pi?</td>
        <td><input type="text" id="valueOfPi"></td>
        <td>double</td>
        <td><textarea class="result" id="doubleAsJson" readonly></textarea></td>
      </tr>

      <tr>
        <td align="right">What's your sign?</td>
        <td><input type="text" id="horoscope"></td>
        <td>String</td>
        <td><textarea class="result" id="stringAsJson" readonly></textarea></td>
      </tr>

      <tr>
        <td align="right">A few of your favorite things?</td>
        <td>
          <input type="text" id="favOne">
          <input type="text" id="favTwo">
          <input type="text" id="favThree">
        </td>
        <td>List&lt;String&gt;</td>
        <td><textarea class="result" id="listAsJson" readonly></textarea></td>
      </tr>

      <tr>
        <td align="right">I love chocolate!</td>
        <td>
          <form>
            <input type="radio" name="chocolate" id="loveChocolate" checked>True
            <input type="radio" name="chocolate" id="noLoveForChocolate" checked>False
          </form>
        </td>
        <td>bool</td>
        <td><textarea class="result" id="boolAsJson" readonly> </textarea></td>
      </tr>

      </tbody>
    </table>

    <div>
      <label>All data together as a map</label><br>
      <textarea id="mapAsJson" readonly></textarea>
    </div>
// 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<String> 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<String> 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

 <h1>Portmanteaux</h1>

  <button id="getWords">Get portmanteaux</button>

  <ul id="wordList">
  </ul>
// 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()函数体和错误处理提供你自己的代码。

注意:这部分例子使用的asyncawait关键字。如果你对这些关键字不熟悉,请看 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<String> 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 

 <h1>Portmanteaux</h1>

  <button id="getWords">Get portmanteaux</button>

  <ul id="wordList">
  </ul>

配置 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,并且把它们添加到页面上的 <ul> 元素中。

从 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 字符串创建指定的字符串对象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 前任,是女生心里抹不去的芥蒂,是男生心里忘不了的牵挂。 曾经的前任,在以后的日子都变成了不愿提及的存在。不论男生还...
    我爱小媛宝阅读 579评论 3 9
  • ReactRouter(github地址)是由 Ryan Florence 开发的, 它是一个针对 React 而...
    lucy_阅读 2,142评论 0 1
  • 继续思考自我,在研读“自控力”中,发现自己就是那个总是活在未来的自己!以前的我经常推迟重要的变化和任务,等待自己变...
    晴声晴语阅读 136评论 1 1