Using Dart with JSON Web Services
Written by Chris Buckett
April 2012 (updated February 2013)
Most client-side Dart apps need a way to communicate with a server, and sending
JSON via XMLHttpRequest is
the preferred way to do this. This article discusses communicating with a
server using the
HttpRequest API
from the dart:html library and
parsing JSON data using the dart:json
library. It then goes on to show how to provide dot-notation access to JSON
data through the use of JsonObject.
Contents
- A JSON web service
- Connecting to the server
- Getting data from the server
- Saving objects on the server
- Parsing JSON
- Introducing JsonObject
- A note on CORS and HttpRequest
- Summary
- Resources
- About the author
A JSON web service
Many modern web apps are powered by RESTful web services that send and receive
data encoded as JSON. This article features a web service that responds to an
HTTP GET request to the URL /programming-languages/dart by returning the
following JSON string, which contains a string, a list, and a map that
represents information about the Dart language:
{
"language": "dart", // String
"targets": ["dartium","javascript"], // List
"website": { // Map
"homepage": "www.dartlang.org",
"api": "api.dartlang.org"
}
}
The same web service accepts data on the same URL with an HTTP POST. The web
service interprets a POST as a request to create a new object on the server,
like an SQL INSERT. The POSTed JSON data is sent in the HTTP body.
Connecting to the server
When communicating with a web service, use the HttpRequest
API from the dart:html library. HttpRequest is a standard way to
programmatically send and receive data to and from web servers. This is
Dart’s equivalent to XMLHttpRequest in JavaScript.
Getting data from the server
Get objects from the server using HTTP GET. HttpRequest provides a named
constructor called get that takes a URL and a callback function
that’s invoked when the server responds.
void loadData() {
var url = "http://127.0.0.1:8080/programming-languages";
// call the web server asynchronously
var request = HttpRequest.getString(url).then(onDataLoaded);
}
Then elsewhere in your code, you can define an onDataLoaded
callback function and call the loadData() function:
// print the raw json response text from the server
void onDataLoaded(String responseText) {
var jsonString = responseText;
print(jsonString);
}
main() {
loadData();
}
Saving objects on the server
To create a new object on the server, use the full HttpRequest API with the
HTTP POST method. Use the readyStateChange listener to be notified when the
request is complete. The example below calls an onSuccess function when the
request is complete:
void saveData() {
HttpRequest request = new HttpRequest(); // create a new XHR
// add an event handler that is called when the request finishes
request.onReadyStateChange.listen((_) {
if (request.readyState == HttpRequest.DONE &&
(request.status == 200 || request.status == 0)) {
// data saved OK.
print(request.responseText); // output the response from the server
}
});
// POST the data to the server
var url = "http://127.0.0.1:8080/programming-languages";
request.open("POST", url, async: false);
String jsonData = '{"language":"dart"}'; // etc...
request.send(jsonData); // perform the async POST
}
Parsing JSON
Now that you have seen how HttpRequest GETs data from the server back to the
client, and POSTs data from the client to the server, the next step is to make
use of the JSON data in the client application.
The dart:json library provides two
top-level functions, parse() and stringify().
The parse() function converts a string containing JSON formatted
text into a List of values or a Map of key-value pairs, depending upon the
content of the JSON:
import 'dart:json';
main() {
String listAsJson = '["Dart",0.8]'; // input List of data
List parsedList = parse(listAsJson);
print(parsedList[0]); // Dart
print(parsedList[1]); // 0.8
String mapAsJson = '{"language":"dart"}'; // input Map of data
Map parsedMap = parse(mapAsJson);
print(parsedMap["language"]); // dart
}
JSON also works for more complex data structures, such as nested maps inside
of lists.
Use parse() to convert the HttpRequest’s response from raw text to an actual Dart Map object:
void onDataLoaded(HttpRequest req) {
Map data = parse(req.responseText); // parse response text
print(data["language"]); // dart
print(data["targets"][0]); // dartium
print(data["website"]["homepage"]); // www.dartlang.org
}
The stringify() function works the same as parse but in reverse.
void saveData() {
// snip setting up HttpRequest
var mapData = new Map();
mapData["language"] = "dart";
mapData["targets"] = new List();
mapData["targets"].add("dartium");
String jsonData = stringify(mapData); // convert map to String
request.send(jsonData); // perform the async POST
}
Using simple Maps with strings as keys has some unfortunate side effects.
Making a typo in any of the string names will return a null value which could
then go on to cause a NoSuchMethodError. Accessing the values from the map
cannot be validated before run-time.
One of the benefits of using Dart is support for optional static types. Static
types help you catch bugs early by allowing tools to detect type mismatches
before you run your code, and to throw exceptions as soon as a runtime issue
occurs. An additional benefit of using static types is that Dart Editor also
uses this type information to provide auto-complete information—helpful
when you are using a new library or data structure.
Ideally, you want to access JSON data in a structured way, taking advantage of
the tools to help you catch bugs early. The following example feels more like
natural Dart code:
var data = // ... initialize data ...
// property access is validated by tools
print(data.language);
print(data.targets[0]);
data.website.forEach((key, value) => print("$key=$value"));
Fortunately, the ability to write code using this “dot notation” is built into
Dart, through its support of classes. The solution, then, is to combine the
flexibility of a Map with the structure of a class.
Introducing JsonObject
This flexibility of JSON and Maps combined with the structure of classes is
made possible with JsonObject, which is a third-party open source library.
JsonObject uses the dart:json parse() function to extract the JSON data into
a map, and then it uses the noSuchMethod feature of Dart classes to provide a
way to access values in the parsed map by using dot notation.
To learn more about JsonObject and download its code, go to the project on
GitHub.
JsonObject uses Dart’s noSuchMethod method support, which enables objects to
intercept unknown method calls. For example, if you invoke a getter such as
data.language, where data is a JsonObject, then behind the scenes
noSuchMethod("get:language", null) is called. Likewise, when you try to set
a value on a JsonObject, noSuchMethod("set:language", ["Dart"]) is called.
JsonObject intercepts the calls to noSuchMethod and accesses the underlying
Map. Data contained within a JsonObject is still Map data, and so the dart:json parse() and stringify() methods still work on JsonObjects.
Here is an example of using JsonObject instead of a raw Map:
void onDataLoaded(HttpRequest req) {
// decode the JSON response text using JsonObject
JsonObject data = new JsonObject.fromJsonString(req.responseText);
// dot notation property access
print(data.language); // Get a simple value
data.language = "Dart"; // Set a simple value
print(data.targets[0]); // Get a value in a list
// iterate the website map
data.website.forEach((key, value) => print("$key=$value"));
}
You can also use this in conjunction with your own classes. By extending
JsonObject, providing a factory constructor and implementing a suitable
interface, you can increase the readability of your code, allow the tools
to help with type checking, and allow your classes to convert back and forth between a JSON string and JsonObject’s internal map structure.
If factory constructors and implementing interfaces sounds like hard work, the following example shows that it really isn’t.
// Abstract class defines the interface of our JSON data structure
abstract class Language {
String language;
List targets;
Map website;
}
/** Implementation class extends JsonObject, and uses the structure
* defined by implementing the Language abstract class.
* JsonObject's noSuchMethod() function provides the actual underlying
* implementation.
*/
class LanguageImpl extends JsonObject implements Language {
LanguageImpl();
factory LanguageImpl.fromJsonString(string) {
return new JsonObject.fromJsonString(string, new LanguageImpl());
}
}
Elsewhere in your code, you can use this structure to get strong typing of your JSON data.
void onDataLoaded(HttpRequest req) {
// Decode the JSON response text using LanguageImpl
// The Language interface provides structure
Language data = new LanguageImpl.fromJsonString(req.responseText);
// dot notation property access
print(data.language); // Get a simple value
data.language = "Dart"; // Set a simple value
print(data.targets[0]); // Get a value in a list
// iterate the website map
data.website.forEach((key, value) => print("$key=$value"));
}
JsonObject also allows you to create new, empty objects, without first
converting from a JSON string, by using the default constructor:
var data = new JsonObject();
data.language = "Dart";
data.targets = new List();
data.targets.add("Dartium");
JsonObject also implements the Map interface, which means that you can use the
standard map syntax:
var data = new JsonObject();
data["language"] = "Dart"; // standard map syntax
Because JsonObject implements Map, you can pass a JsonObject into
stringify(), which converts a Map into JSON for sending the data back to
the server:
var data = new JsonObject.fromJsonString(req.responseText);
// later...
// convert the JsonObject data back to a string
String json = stringify(data);
// and POST it back to the server
HttpRequest req = new HttpRequest();
req.open("POST", url);
req.send(json);
You can include JsonObject in your project by using the
pub package manager.
Simply specify the following dependency:
dependencies:
json_object: any
and import the package using the following import statement:
import "package:json_object/json_object.dart";
A note on CORS and HttpRequest
One caveat: Make sure your app is served from the same origin (domain name,
port, and application layer protocol) as the web service you are trying to
access with HttpRequest. Otherwise your app will hit the
Access-Control-Allow-Origin restriction build into your web
browser. This is a security restriction to prevent loading data from a
different server than the one serving the client app.
You can get around this restriction in a couple of ways. The first is to use
an emerging technology known as
Cross-Origin Resource Sharing
(CORS), which is starting to become implemented by web servers. In the code
that accompanies this article,
you can find simpleserver.dart that serves example JSON data for this article.
This makes use of CORS headers to allow access from a different URL, such as
the automatically generated URL of apps launched from the Dart editor.
The second, older way, that only works for GET requests is to use a workaround
called JSONP, which makes use of JavaScript callbacks.
The Dart - JavaScript interop libraries in the js
interop package available on pub
are suitable for JavaScript callbacks:
import 'dart:html';
import 'package:js/js.dart' as js;
void main() {
js.scoped(() {
// create a top-level JavaScript function called myJsonpCallback
js.context.myJsonpCallback = new js.Callback.once( (jsonData) {
print(jsonData); // js.Proxy object containing the data
// see js interop docs
});
// add a script tag for the api required
ScriptElement script = new Element.tag("script");
// add the callback function name to the URL
script.src = "http://example.com/some/api?callback=myJsonpCallback";
document.body.children.add(script); // add the script to the DOM
});
}
For more detailed information about JS Interop, see
the js package docs.
Summary
This article showed how a client-side Dart application communicates with a
JSON-based web service via HTTP GET and POST. JSON data is parsed using the
dart:json library, which converts JSON strings into maps and lists. Using
JsonObject with the JSON data allows you to extend the functionality of the
dart:json library by letting you use dot notation to access data fields.
Resources
About the author

Chris Buckett is a Technical Manager for
Entity Group Ltd, responsible for building and
delivering enterprise client-server webapps, mostly with GWT, Java and .Net.
He runs the dartwatch.com blog, and has written
the writing the book Dart in Action, which is available
at manning.com.