Backed out 6 changesets (bug 1396821) for linux tier2 build bustages on a CLOSED TREE
authorAndreea Pavel <apavel@mozilla.com>
Tue, 21 Aug 2018 20:20:24 +0300
changeset 432625 d8a7212c51a48253bfa9551ecc9318a9536c62d4
parent 432624 07dabfefeef1dc4e0a7263bb10a504fa8f8252a0
child 432626 ec7da761af74dacb9a4172ac2e2a42de032bd4ef
push id106808
push userapavel@mozilla.com
push dateTue, 21 Aug 2018 17:20:40 +0000
treeherdermozilla-inbound@d8a7212c51a4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1396821
milestone63.0a1
backs out2fa2975f97e3e81f501aa982ab224ac470683f51
c5895db52483b9fd5d2e0df6498f5aa601573d37
5c0ddd45f926a15d7d90f574f706fcc7f33d1e3b
7c97853a85b94dc65cfda23a8ee6f826a606505d
b61ce753f01e0c1a2ae06c58058880e653a857f0
7ef3912feb2c844edabbdb6376838d046432bc22
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 6 changesets (bug 1396821) for linux tier2 build bustages on a CLOSED TREE Backed out changeset 2fa2975f97e3 (bug 1396821) Backed out changeset c5895db52483 (bug 1396821) Backed out changeset 5c0ddd45f926 (bug 1396821) Backed out changeset 7c97853a85b9 (bug 1396821) Backed out changeset b61ce753f01e (bug 1396821) Backed out changeset 7ef3912feb2c (bug 1396821)
Cargo.lock
testing/geckodriver/Cargo.toml
testing/geckodriver/src/build.rs
testing/geckodriver/src/capabilities.rs
testing/geckodriver/src/logging.rs
testing/geckodriver/src/main.rs
testing/geckodriver/src/marionette.rs
testing/geckodriver/src/test.rs
testing/web-platform/meta/webdriver/tests/new_session/merge.py.ini
testing/webdriver/Cargo.toml
testing/webdriver/src/actions.rs
testing/webdriver/src/capabilities.rs
testing/webdriver/src/command.rs
testing/webdriver/src/common.rs
testing/webdriver/src/error.rs
testing/webdriver/src/httpapi.rs
testing/webdriver/src/lib.rs
testing/webdriver/src/macros.rs
testing/webdriver/src/response.rs
testing/webdriver/src/server.rs
testing/webdriver/src/test.rs
third_party/rust/rustc-serialize/.cargo-checksum.json
third_party/rust/rustc-serialize/.travis.yml
third_party/rust/rustc-serialize/Cargo.toml
third_party/rust/rustc-serialize/LICENSE-APACHE
third_party/rust/rustc-serialize/LICENSE-MIT
third_party/rust/rustc-serialize/README.md
third_party/rust/rustc-serialize/appveyor.yml
third_party/rust/rustc-serialize/benches/base64.rs
third_party/rust/rustc-serialize/benches/hex.rs
third_party/rust/rustc-serialize/benches/json.rs
third_party/rust/rustc-serialize/src/base64.rs
third_party/rust/rustc-serialize/src/collection_impls.rs
third_party/rust/rustc-serialize/src/hex.rs
third_party/rust/rustc-serialize/src/json.rs
third_party/rust/rustc-serialize/src/lib.rs
third_party/rust/rustc-serialize/src/serialize.rs
third_party/rust/ryu/.cargo-checksum.json
third_party/rust/ryu/.travis.yml
third_party/rust/ryu/Cargo.toml
third_party/rust/ryu/LICENSE-APACHE
third_party/rust/ryu/README.md
third_party/rust/ryu/benchmark/benchmark.rs
third_party/rust/ryu/build.rs
third_party/rust/ryu/src/buffer/mod.rs
third_party/rust/ryu/src/common.rs
third_party/rust/ryu/src/d2s.rs
third_party/rust/ryu/src/d2s_full_table.rs
third_party/rust/ryu/src/d2s_small_table.rs
third_party/rust/ryu/src/digit_table.rs
third_party/rust/ryu/src/f2s.rs
third_party/rust/ryu/src/lib.rs
third_party/rust/ryu/src/mulshift128.rs
third_party/rust/ryu/src/pretty/exponent.rs
third_party/rust/ryu/src/pretty/mantissa.rs
third_party/rust/ryu/src/pretty/mod.rs
third_party/rust/ryu/tests/d2s_table_test.rs
third_party/rust/ryu/tests/d2s_test.rs
third_party/rust/ryu/tests/exhaustive.rs
third_party/rust/ryu/tests/f2s_test.rs
third_party/rust/ryu/tests/macros/mod.rs
third_party/rust/serde_json/.cargo-checksum.json
third_party/rust/serde_json/Cargo.toml
third_party/rust/serde_json/LICENSE-APACHE
third_party/rust/serde_json/LICENSE-MIT
third_party/rust/serde_json/README.md
third_party/rust/serde_json/src/de.rs
third_party/rust/serde_json/src/error.rs
third_party/rust/serde_json/src/iter.rs
third_party/rust/serde_json/src/lib.rs
third_party/rust/serde_json/src/macros.rs
third_party/rust/serde_json/src/map.rs
third_party/rust/serde_json/src/number.rs
third_party/rust/serde_json/src/read.rs
third_party/rust/serde_json/src/ser.rs
third_party/rust/serde_json/src/value/de.rs
third_party/rust/serde_json/src/value/from.rs
third_party/rust/serde_json/src/value/index.rs
third_party/rust/serde_json/src/value/mod.rs
third_party/rust/serde_json/src/value/partial_eq.rs
third_party/rust/serde_json/src/value/ser.rs
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -801,29 +801,26 @@ dependencies = [
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "geckodriver"
 version = "0.21.0"
 dependencies = [
- "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
  "clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "mozprofile 0.3.0",
  "mozrunner 0.7.0",
  "mozversion 0.1.3",
  "regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.66 (git+https://github.com/servo/serde?branch=deserialize_from_enums8)",
- "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "webdriver 0.36.0",
  "zip 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "geckoservo"
 version = "0.0.1"
@@ -1835,29 +1832,29 @@ version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "rustc-demangle"
 version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "rustc-serialize"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
 name = "rustc_version"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "ryu"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
 name = "safemem"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "same-file"
 version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1927,26 +1924,16 @@ version = "1.0.66"
 source = "git+https://github.com/servo/serde?branch=deserialize_from_enums8#c4457d804b38b14e699b45c01d1909f93f25ab5e"
 dependencies = [
  "proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "serde_json"
-version = "1.0.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "ryu 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
 name = "servo_arc"
 version = "0.1.1"
 dependencies = [
  "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2420,25 +2407,21 @@ dependencies = [
  "same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webdriver"
 version = "0.36.0"
 dependencies = [
- "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cookie 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.66 (git+https://github.com/servo/serde?branch=deserialize_from_enums8)",
- "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webidl"
 version = "0.6.0"
@@ -2807,28 +2790,27 @@ dependencies = [
 "checksum regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75ecf88252dce580404a22444fc7d626c01815debba56a7f4f536772a5ff19d3"
 "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
 "checksum regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1ac0f60d675cc6cf13a20ec076568254472551051ad5dd050364d70671bf6b"
 "checksum rkv 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "21983ae9330b1e1cb1d01868229618a3c7cc5134955f0dc1a86a0a1886f3acb7"
 "checksum ron 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "da06feaa07f69125ab9ddc769b11de29090122170b402547f64b86fe16ebc399"
 "checksum runloop 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d79b4b604167921892e84afbbaad9d5ad74e091bf6c511d9dbfb0593f09fabd"
 "checksum rust-ini 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8a654c5bda722c699be6b0fe4c0d90de218928da5b724c3e467fc48865c37263"
 "checksum rustc-demangle 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "76d7ba1feafada44f2d38eed812bd2489a03c0f5abb975799251518b68848649"
+"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
 "checksum rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9743a7670d88d5d52950408ecdb7c71d8986251ab604d4689dd2ca25c9bca69"
-"checksum ryu 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0568787116e13c652377b6846f5931454a363a8fdf8ae50463ee40935b278b"
 "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
 "checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
 "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d"
 "checksum scopeguard 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c79eb2c3ac4bc2507cda80e7f3ac5b88bd8eae4c0914d5663e6a8933994be918"
 "checksum semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537"
 "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 "checksum serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "e9a2d9a9ac5120e0f768801ca2b58ad6eec929dc9d1d616c162f208869c2ce95"
 "checksum serde_bytes 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "adb6e51a6b3696b301bc221d785f898b4457c619b51d7ce195a6d20baecb37b3"
 "checksum serde_derive 1.0.66 (git+https://github.com/servo/serde?branch=deserialize_from_enums8)" = "<none>"
-"checksum serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "44dd2cfde475037451fa99b7e5df77aa3cfd1536575fa8e7a538ab36dcde49ae"
 "checksum simd 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3686dd9418ebcc3a26a0c0ae56deab0681e53fe899af91f5bbcee667ebffb1"
 "checksum siphasher 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ffc669b726f2bc9a3bcff66e5e23b56ba6bf70e22a34c3d7b6d0b3450b65b84"
 "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
 "checksum smallbitvec 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c63726029f0069f88467873e47f392575f28f9f16b72ac65465263db4b3a13c"
 "checksum smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "26df3bb03ca5eac2e64192b723d51f56c1b1e0860e7c766281f4598f181acdc8"
 "checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b"
 "checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423"
 "checksum string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "479cde50c3539481f33906a387f2bd17c8e87cb848c35b6021d41fb81ff9b4d7"
--- a/testing/geckodriver/Cargo.toml
+++ b/testing/geckodriver/Cargo.toml
@@ -4,27 +4,24 @@ version = "0.21.0"
 description = "Proxy for using WebDriver clients to interact with Gecko-based browsers."
 keywords = ["webdriver", "w3c", "httpd", "mozilla", "firefox"]
 repository = "https://hg.mozilla.org/mozilla-central/file/tip/testing/geckodriver"
 readme = "README.md"
 license = "MPL-2.0"
 publish = false
 
 [dependencies]
-base64 = "0.6"
 chrono = "^0.2"
 clap = { version = "^2.19", default-features = false, features = ["suggestions", "wrap_help"] }
 hyper = "0.10"
 lazy_static = "1.0"
 log = { version = "0.4", features = ["std"] }
 mozprofile = { path = "../mozbase/rust/mozprofile" }
 mozrunner = { path = "../mozbase/rust/mozrunner" }
 mozversion = { path = "../mozbase/rust/mozversion" }
 regex = "1.0"
-serde = "1.0"
-serde_json = "1.0"
-serde_derive = "1.0"
+rustc-serialize = "0.3"
 uuid = { version = "0.5", features = ["v4"] }
 webdriver = { path = "../webdriver" }
 zip = "0.3"
 
 [[bin]]
 name = "geckodriver"
--- a/testing/geckodriver/src/build.rs
+++ b/testing/geckodriver/src/build.rs
@@ -1,11 +1,12 @@
-use serde_json::Value;
 use std::fmt;
 
+use rustc_serialize::json::{Json};
+
 include!(concat!(env!("OUT_DIR"), "/build-info.rs"));
 
 pub struct BuildInfo;
 
 impl BuildInfo {
     pub fn version() -> &'static str {
         crate_version!()
     }
@@ -26,15 +27,15 @@ impl fmt::Display for BuildInfo {
             (Some(hash), Some(date)) => write!(f, " ({} {})", hash, date)?,
             (Some(hash), None) => write!(f, " ({})", hash)?,
             _ => {}
         }
         Ok(())
     }
 }
 
-// TODO(Henrik): Change into From
 //std::convert::From<&str>` is not implemented for `rustc_serialize::json::Json
-impl Into<Value> for BuildInfo {
-    fn into(self) -> Value {
-        Value::String(BuildInfo::version().to_string())
+
+impl Into<Json> for BuildInfo {
+    fn into(self) -> Json {
+        Json::String(BuildInfo::version().to_string())
     }
 }
--- a/testing/geckodriver/src/capabilities.rs
+++ b/testing/geckodriver/src/capabilities.rs
@@ -1,24 +1,24 @@
-use base64;
 use logging::Level;
 use marionette::LogOptions;
 use mozprofile::preferences::Pref;
 use mozprofile::profile::Profile;
 use mozrunner::runner::platform::firefox_default_path;
 use mozversion::{self, firefox_version, Version};
 use regex::bytes::Regex;
-use serde_json::{Map, Value};
+use rustc_serialize::base64::FromBase64;
+use rustc_serialize::json::Json;
 use std::collections::BTreeMap;
 use std::default::Default;
 use std::error::Error;
 use std::fs;
-use std::io;
 use std::io::BufWriter;
 use std::io::Cursor;
+use std::io;
 use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
 use std::str::{self, FromStr};
 use webdriver::capabilities::{BrowserCapabilities, Capabilities};
 use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
 use zip;
 
 /// Provides matching of `moz:firefoxOptions` and resolution of which Firefox
@@ -29,30 +29,31 @@ use zip;
 /// system Firefox installation or an override, for example given to the
 /// `--binary` flag of geckodriver.
 pub struct FirefoxCapabilities<'a> {
     pub chosen_binary: Option<PathBuf>,
     fallback_binary: Option<&'a PathBuf>,
     version_cache: BTreeMap<PathBuf, String>,
 }
 
+
 impl<'a> FirefoxCapabilities<'a> {
     pub fn new(fallback_binary: Option<&'a PathBuf>) -> FirefoxCapabilities<'a> {
         FirefoxCapabilities {
             chosen_binary: None,
             fallback_binary: fallback_binary,
             version_cache: BTreeMap::new(),
         }
     }
 
-    fn set_binary(&mut self, capabilities: &Map<String, Value>) {
+    fn set_binary(&mut self, capabilities: &BTreeMap<String, Json>) {
         self.chosen_binary = capabilities
             .get("moz:firefoxOptions")
-            .and_then(|x| x.get("binary"))
-            .and_then(|x| x.as_str())
+            .and_then(|x| x.find("binary"))
+            .and_then(|x| x.as_string())
             .map(|x| PathBuf::from(x))
             .or_else(|| self.fallback_binary.map(|x| x.clone()))
             .or_else(|| firefox_default_path())
     }
 
     fn version(&mut self) -> Option<String> {
         if let Some(ref binary) = self.chosen_binary {
             if let Some(value) = self.version_cache.get(binary) {
@@ -63,54 +64,52 @@ impl<'a> FirefoxCapabilities<'a> {
                 .ok()
                 .and_then(|x| x.version_string)
                 .or_else(|| {
                     debug!("Trying to read firefox version from binary");
                     self.version_from_binary(binary)
                 });
             if let Some(ref version) = rv {
                 debug!("Found version {}", version);
-                self.version_cache.insert(binary.clone(), version.clone());
+                self.version_cache
+                    .insert(binary.clone(), version.clone());
             } else {
                 debug!("Failed to get binary version");
             }
             rv
         } else {
             None
         }
     }
 
     fn version_from_binary(&self, binary: &PathBuf) -> Option<String> {
-        let version_regexp =
-            Regex::new(r#"\d+\.\d+(?:[a-z]\d+)?"#).expect("Error parsing version regexp");
+        let version_regexp = Regex::new(r#"\d+\.\d+(?:[a-z]\d+)?"#).expect("Error parsing version regexp");
         let output = Command::new(binary)
             .args(&["-version"])
             .stdout(Stdio::piped())
             .spawn()
             .and_then(|child| child.wait_with_output())
             .ok();
 
         if let Some(x) = output {
-            version_regexp
-                .captures(&*x.stdout)
+            version_regexp.captures(&*x.stdout)
                 .and_then(|captures| captures.get(0))
                 .and_then(|m| str::from_utf8(m.as_bytes()).ok())
                 .map(|x| x.into())
         } else {
             None
         }
     }
 }
 
 // TODO: put this in webdriver-rust
 fn convert_version_error(err: mozversion::Error) -> WebDriverError {
     WebDriverError::new(
         ErrorStatus::SessionNotCreated,
-        err.description().to_string(),
-    )
+        err.description().to_string())
 }
 
 impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> {
     fn init(&mut self, capabilities: &Capabilities) {
         self.set_binary(capabilities);
     }
 
     fn browser_name(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>> {
@@ -118,18 +117,18 @@ impl<'a> BrowserCapabilities for Firefox
     }
 
     fn browser_version(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>> {
         Ok(self.version())
     }
 
     fn platform_name(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>> {
         Ok(if cfg!(target_os = "windows") {
-            Some("windows".into())
-        } else if cfg!(target_os = "macos") {
+               Some("windows".into())
+           } else if cfg!(target_os = "macos") {
             Some("mac".into())
         } else if cfg!(target_os = "linux") {
             Some("linux".into())
         } else {
             None
         })
     }
 
@@ -141,260 +140,224 @@ impl<'a> BrowserCapabilities for Firefox
             Ok(false)
         }
     }
 
     fn set_window_rect(&mut self, _: &Capabilities) -> WebDriverResult<bool> {
         Ok(true)
     }
 
-    fn compare_browser_version(
-        &mut self,
-        version: &str,
-        comparison: &str,
-    ) -> WebDriverResult<bool> {
+    fn compare_browser_version(&mut self,
+                               version: &str,
+                               comparison: &str)
+                               -> WebDriverResult<bool> {
         try!(Version::from_str(version).or_else(|x| Err(convert_version_error(x))))
             .matches(comparison)
             .or_else(|x| Err(convert_version_error(x)))
     }
 
     fn accept_proxy(&mut self, _: &Capabilities, _: &Capabilities) -> WebDriverResult<bool> {
         Ok(true)
     }
 
-    fn validate_custom(&self, name: &str, value: &Value) -> WebDriverResult<()> {
+    fn validate_custom(&self, name: &str,  value: &Json) -> WebDriverResult<()> {
         if !name.starts_with("moz:") {
-            return Ok(());
+            return Ok(())
         }
         match name {
             "moz:firefoxOptions" => {
-                let data = try_opt!(
-                    value.as_object(),
-                    ErrorStatus::InvalidArgument,
-                    "moz:firefoxOptions is not an object"
-                );
+                let data = try_opt!(value.as_object(),
+                                    ErrorStatus::InvalidArgument,
+                                    "moz:firefoxOptions is not an object");
                 for (key, value) in data.iter() {
                     match &**key {
                         "binary" => {
                             if !value.is_string() {
                                 return Err(WebDriverError::new(
                                     ErrorStatus::InvalidArgument,
-                                    "binary path is not a string",
-                                ));
+                                         "binary path is not a string"));
                             }
-                        }
+                        },
                         "args" => {
-                            if !try_opt!(
-                                value.as_array(),
-                                ErrorStatus::InvalidArgument,
-                                "args is not an array"
-                            ).iter()
-                                .all(|value| value.is_string())
-                            {
+                            if !try_opt!(value.as_array(),
+                                         ErrorStatus::InvalidArgument,
+                                         "args is not an array")
+                                .iter()
+                                .all(|value| value.is_string()) {
                                 return Err(WebDriverError::new(
                                     ErrorStatus::InvalidArgument,
-                                    "args entry is not a string",
-                                ));
-                            }
-                        }
+                                         "args entry is not a string"));
+                                }
+                        },
                         "profile" => {
                             if !value.is_string() {
                                 return Err(WebDriverError::new(
                                     ErrorStatus::InvalidArgument,
-                                    "profile is not a string",
-                                ));
+                                         "profile is not a string"));
                             }
-                        }
+                        },
                         "log" => {
-                            let log_data = try_opt!(
-                                value.as_object(),
-                                ErrorStatus::InvalidArgument,
-                                "log value is not an object"
-                            );
+                            let log_data = try_opt!(value.as_object(),
+                                                    ErrorStatus::InvalidArgument,
+                                                    "log value is not an object");
                             for (log_key, log_value) in log_data.iter() {
                                 match &**log_key {
                                     "level" => {
-                                        let level = try_opt!(
-                                            log_value.as_str(),
-                                            ErrorStatus::InvalidArgument,
-                                            "log level is not a string"
-                                        );
+                                        let level = try_opt!(log_value.as_string(),
+                                                             ErrorStatus::InvalidArgument,
+                                                             "log level is not a string");
                                         if Level::from_str(level).is_err() {
                                             return Err(WebDriverError::new(
                                                 ErrorStatus::InvalidArgument,
-                                                format!("Not a valid log level: {}", level),
-                                            ));
+                                                format!("Not a valid log level: {}", level)))
                                         }
                                     }
-                                    x => {
-                                        return Err(WebDriverError::new(
-                                            ErrorStatus::InvalidArgument,
-                                            format!("Invalid log field {}", x),
-                                        ))
-                                    }
+                                    x => return Err(WebDriverError::new(
+                                        ErrorStatus::InvalidArgument,
+                                        format!("Invalid log field {}", x)))
                                 }
                             }
-                        }
+                        },
                         "prefs" => {
-                            let prefs_data = try_opt!(
-                                value.as_object(),
-                                ErrorStatus::InvalidArgument,
-                                "prefs value is not an object"
-                            );
-                            if !prefs_data.values().all(|x| {
-                                x.is_string() || x.is_i64() || x.is_u64() || x.is_boolean()
-                            }) {
-                                return Err(WebDriverError::new(
-                                    ErrorStatus::InvalidArgument,
-                                    "Preference values not all string or integer or boolean",
-                                ));
-                            }
+                            let prefs_data = try_opt!(value.as_object(),
+                                                    ErrorStatus::InvalidArgument,
+                                                    "prefs value is not an object");
+                            if !prefs_data.values()
+                                .all(|x| x.is_string() || x.is_i64() || x.is_u64() || x.is_boolean()) {
+                                    return Err(WebDriverError::new(
+                                        ErrorStatus::InvalidArgument,
+                                        "Preference values not all string or integer or boolean"));
+                                }
                         }
-                        x => {
-                            return Err(WebDriverError::new(
-                                ErrorStatus::InvalidArgument,
-                                format!("Invalid moz:firefoxOptions field {}", x),
-                            ))
-                        }
+                        x => return Err(WebDriverError::new(
+                            ErrorStatus::InvalidArgument,
+                            format!("Invalid moz:firefoxOptions field {}", x)))
                     }
                 }
             }
             "moz:useNonSpecCompliantPointerOrigin" => {
                 if !value.is_boolean() {
                     return Err(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
-                        "moz:useNonSpecCompliantPointerOrigin is not a boolean",
-                    ));
+                        "moz:useNonSpecCompliantPointerOrigin is not a boolean"));
                 }
             }
             "moz:webdriverClick" => {
                 if !value.is_boolean() {
                     return Err(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
-                        "moz:webdriverClick is not a boolean",
-                    ));
+                        "moz:webdriverClick is not a boolean"));
                 }
             }
-            _ => {
-                return Err(WebDriverError::new(
-                    ErrorStatus::InvalidArgument,
-                    format!("Unrecognised option {}", name),
-                ))
-            }
+            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                format!("Unrecognised option {}", name)))
         }
         Ok(())
     }
 
-    fn accept_custom(&mut self, _: &str, _: &Value, _: &Capabilities) -> WebDriverResult<bool> {
+    fn accept_custom(&mut self, _: &str, _: &Json, _: &Capabilities) -> WebDriverResult<bool> {
         Ok(true)
     }
 }
 
 /// Rust representation of `moz:firefoxOptions`.
 ///
 /// Calling `FirefoxOptions::from_capabilities(binary, capabilities)` causes
 /// the encoded profile, the binary arguments, log settings, and additional
 /// preferences to be checked and unmarshaled from the `moz:firefoxOptions`
 /// JSON Object into a Rust representation.
-#[derive(Default, Debug)]
+#[derive(Default)]
 pub struct FirefoxOptions {
     pub binary: Option<PathBuf>,
     pub profile: Option<Profile>,
     pub args: Option<Vec<String>>,
     pub log: LogOptions,
     pub prefs: Vec<(String, Pref)>,
 }
 
 impl FirefoxOptions {
     pub fn new() -> FirefoxOptions {
         Default::default()
     }
 
-    pub fn from_capabilities(
-        binary_path: Option<PathBuf>,
-        matched: &mut Capabilities,
-    ) -> WebDriverResult<FirefoxOptions> {
+    pub fn from_capabilities(binary_path: Option<PathBuf>,
+                             matched: &mut Capabilities)
+                             -> WebDriverResult<FirefoxOptions> {
         let mut rv = FirefoxOptions::new();
         rv.binary = binary_path;
 
         if let Some(json) = matched.remove("moz:firefoxOptions") {
-            let options = try!(json.as_object().ok_or(WebDriverError::new(
-                ErrorStatus::InvalidArgument,
-                "'moz:firefoxOptions' \
-                 capability is not an object"
-            )));
+            let options = try!(json.as_object()
+                                   .ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                              "'moz:firefoxOptions' \
+                                                               capability is not an object")));
 
             rv.profile = try!(FirefoxOptions::load_profile(&options));
             rv.args = try!(FirefoxOptions::load_args(&options));
             rv.log = try!(FirefoxOptions::load_log(&options));
             rv.prefs = try!(FirefoxOptions::load_prefs(&options));
         }
 
         Ok(rv)
     }
 
     fn load_profile(options: &Capabilities) -> WebDriverResult<Option<Profile>> {
         if let Some(profile_json) = options.get("profile") {
-            let profile_base64 = try!(profile_json.as_str().ok_or(WebDriverError::new(
-                ErrorStatus::UnknownError,
-                "Profile is not a string"
-            )));
-            let profile_zip = &*try!(base64::decode(profile_base64));
+            let profile_base64 =
+                try!(profile_json
+                         .as_string()
+                         .ok_or(WebDriverError::new(ErrorStatus::UnknownError,
+                                                    "Profile is not a string")));
+            let profile_zip = &*try!(profile_base64.from_base64());
 
             // Create an emtpy profile directory
             let profile = try!(Profile::new(None));
-            try!(unzip_buffer(
-                profile_zip,
-                profile
-                    .temp_dir
-                    .as_ref()
-                    .expect("Profile doesn't have a path")
-                    .path()
-            ));
+            try!(unzip_buffer(profile_zip,
+                              profile
+                                  .temp_dir
+                                  .as_ref()
+                                  .expect("Profile doesn't have a path")
+                                  .path()));
 
             Ok(Some(profile))
         } else {
             Ok(None)
         }
     }
 
     fn load_args(options: &Capabilities) -> WebDriverResult<Option<Vec<String>>> {
         if let Some(args_json) = options.get("args") {
-            let args_array = try!(args_json.as_array().ok_or(WebDriverError::new(
-                ErrorStatus::UnknownError,
-                "Arguments were not an \
-                 array"
-            )));
-            let args = try!(
-                args_array
-                    .iter()
-                    .map(|x| x.as_str().map(|x| x.to_owned()))
-                    .collect::<Option<Vec<String>>>()
-                    .ok_or(WebDriverError::new(
-                        ErrorStatus::UnknownError,
-                        "Arguments entries were not all \
-                         strings"
-                    ))
-            );
+            let args_array = try!(args_json
+                                      .as_array()
+                                      .ok_or(WebDriverError::new(ErrorStatus::UnknownError,
+                                                                 "Arguments were not an \
+                                                                  array")));
+            let args = try!(args_array
+                                .iter()
+                                .map(|x| x.as_string().map(|x| x.to_owned()))
+                                .collect::<Option<Vec<String>>>()
+                                .ok_or(WebDriverError::new(ErrorStatus::UnknownError,
+                                                           "Arguments entries were not all \
+                                                            strings")));
             Ok(Some(args))
         } else {
             Ok(None)
         }
     }
 
     fn load_log(options: &Capabilities) -> WebDriverResult<LogOptions> {
         if let Some(json) = options.get("log") {
             let log = json.as_object().ok_or(WebDriverError::new(
                 ErrorStatus::InvalidArgument,
                 "Log section is not an object",
             ))?;
 
             let level = match log.get("level") {
                 Some(json) => {
-                    let s = json.as_str().ok_or(WebDriverError::new(
+                    let s = json.as_string().ok_or(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
                         "Log level is not a string",
                     ))?;
                     Some(Level::from_str(s).ok().ok_or(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
                         "Log level is unknown",
                     ))?)
                 }
@@ -404,55 +367,52 @@ impl FirefoxOptions {
             Ok(LogOptions { level })
         } else {
             Ok(Default::default())
         }
     }
 
     pub fn load_prefs(options: &Capabilities) -> WebDriverResult<Vec<(String, Pref)>> {
         if let Some(prefs_data) = options.get("prefs") {
-            let prefs = try!(prefs_data.as_object().ok_or(WebDriverError::new(
-                ErrorStatus::UnknownError,
-                "Prefs were not an object"
-            )));
+            let prefs = try!(prefs_data
+                                 .as_object()
+                                 .ok_or(WebDriverError::new(ErrorStatus::UnknownError,
+                                                            "Prefs were not an object")));
             let mut rv = Vec::with_capacity(prefs.len());
             for (key, value) in prefs.iter() {
                 rv.push((key.clone(), try!(pref_from_json(value))));
             }
             Ok(rv)
         } else {
             Ok(vec![])
         }
     }
 }
 
-fn pref_from_json(value: &Value) -> WebDriverResult<Pref> {
+fn pref_from_json(value: &Json) -> WebDriverResult<Pref> {
     match value {
-        &Value::String(ref x) => Ok(Pref::new(x.clone())),
-        &Value::Number(ref x) => Ok(Pref::new(x.as_i64().unwrap())),
-        &Value::Bool(x) => Ok(Pref::new(x)),
-        _ => Err(WebDriverError::new(
-            ErrorStatus::UnknownError,
-            "Could not convert pref value to string, boolean, or integer",
-        )),
+        &Json::String(ref x) => Ok(Pref::new(x.clone())),
+        &Json::I64(x) => Ok(Pref::new(x)),
+        &Json::U64(x) => Ok(Pref::new(x as i64)),
+        &Json::Boolean(x) => Ok(Pref::new(x)),
+        _ => Err(WebDriverError::new(ErrorStatus::UnknownError,
+                                     "Could not convert pref value to string, boolean, or integer"))
     }
 }
 
 fn unzip_buffer(buf: &[u8], dest_dir: &Path) -> WebDriverResult<()> {
     let reader = Cursor::new(buf);
-    let mut zip = try!(
-        zip::ZipArchive::new(reader)
-            .map_err(|_| WebDriverError::new(ErrorStatus::UnknownError, "Failed to unzip profile"))
-    );
+    let mut zip = try!(zip::ZipArchive::new(reader).map_err(|_| {
+        WebDriverError::new(ErrorStatus::UnknownError, "Failed to unzip profile")
+    }));
 
     for i in 0..zip.len() {
-        let mut file = try!(zip.by_index(i).map_err(|_| WebDriverError::new(
-            ErrorStatus::UnknownError,
-            "Processing profile zip file failed"
-        )));
+        let mut file = try!(zip.by_index(i).map_err(|_| {
+            WebDriverError::new(ErrorStatus::UnknownError, "Processing profile zip file failed")
+        }));
         let unzip_path = {
             let name = file.name();
             let is_dir = name.ends_with("/");
             let rel_path = Path::new(name);
             let dest_path = dest_dir.join(rel_path);
 
             {
                 let create_dir = if is_dir {
@@ -486,70 +446,78 @@ fn unzip_buffer(buf: &[u8], dest_dir: &P
     }
 
     Ok(())
 }
 
 #[cfg(test)]
 mod tests {
     extern crate mozprofile;
+    extern crate rustc_serialize;
 
     use self::mozprofile::preferences::Pref;
-    use super::*;
+    use self::rustc_serialize::base64::{CharacterSet, Config, Newline, ToBase64};
+    use self::rustc_serialize::json::Json;
+    use super::FirefoxOptions;
     use marionette::MarionetteHandler;
+    use std::collections::BTreeMap;
     use std::default::Default;
     use std::fs::File;
     use std::io::Read;
 
     use webdriver::capabilities::Capabilities;
 
-    fn example_profile() -> Value {
+    fn example_profile() -> Json {
         let mut profile_data = Vec::with_capacity(1024);
         let mut profile = File::open("src/tests/profile.zip").unwrap();
         profile.read_to_end(&mut profile_data).unwrap();
-        Value::String(base64::encode(&profile_data))
+        let base64_config = Config {
+            char_set: CharacterSet::Standard,
+            newline: Newline::LF,
+            pad: true,
+            line_length: None,
+        };
+        Json::String(profile_data.to_base64(base64_config))
     }
 
     fn make_options(firefox_opts: Capabilities) -> FirefoxOptions {
         let mut caps = Capabilities::new();
-        caps.insert("moz:firefoxOptions".into(), Value::Object(firefox_opts));
+        caps.insert("moz:firefoxOptions".into(), Json::Object(firefox_opts));
         let binary = None;
         FirefoxOptions::from_capabilities(binary, &mut caps).unwrap()
     }
 
     #[test]
     fn test_profile() {
         let encoded_profile = example_profile();
         let mut firefox_opts = Capabilities::new();
         firefox_opts.insert("profile".into(), encoded_profile);
 
         let opts = make_options(firefox_opts);
         let mut profile = opts.profile.unwrap();
         let prefs = profile.user_prefs().unwrap();
 
         println!("{:#?}", prefs.prefs);
 
-        assert_eq!(
-            prefs.get("startup.homepage_welcome_url"),
-            Some(&Pref::new("data:text/html,PASS"))
-        );
+        assert_eq!(prefs.get("startup.homepage_welcome_url"),
+                   Some(&Pref::new("data:text/html,PASS")));
     }
 
     #[test]
     fn test_prefs() {
         let encoded_profile = example_profile();
-        let mut prefs: Map<String, Value> = Map::new();
+        let mut prefs: BTreeMap<String, Json> = BTreeMap::new();
         prefs.insert(
             "browser.display.background_color".into(),
-            Value::String("#00ff00".into()),
+            Json::String("#00ff00".into()),
         );
 
         let mut firefox_opts = Capabilities::new();
         firefox_opts.insert("profile".into(), encoded_profile);
-        firefox_opts.insert("prefs".into(), Value::Object(prefs));
+        firefox_opts.insert("prefs".into(), Json::Object(prefs));
 
         let opts = make_options(firefox_opts);
         let mut profile = opts.profile.unwrap();
 
         let handler = MarionetteHandler::new(Default::default());
         handler
             .set_prefs(2828, &mut profile, true, opts.prefs)
             .unwrap();
--- a/testing/geckodriver/src/logging.rs
+++ b/testing/geckodriver/src/logging.rs
@@ -274,20 +274,17 @@ mod tests {
             (Level::Warn, "Warn"),
             (Level::Info, "Info"),
             (Level::Config, "Config"),
             (Level::Debug, "Debug"),
             (Level::Trace, "Trace"),
         ];
 
         for &(lvl, s) in tests.iter() {
-            let expected = Pref {
-                value: PrefValue::String(s.to_string()),
-                sticky: false,
-            };
+            let expected = Pref { value: PrefValue::String(s.to_string()), sticky: false };
             assert_eq!(Into::<Pref>::into(lvl), expected);
         }
     }
 
     #[test]
     fn test_level_from_str() {
         assert_eq!(Level::from_str("fatal"), Ok(Level::Fatal));
         assert_eq!(Level::from_str("error"), Ok(Level::Error));
--- a/testing/geckodriver/src/main.rs
+++ b/testing/geckodriver/src/main.rs
@@ -1,57 +1,50 @@
-extern crate base64;
 extern crate chrono;
 #[macro_use]
 extern crate clap;
 #[macro_use]
 extern crate lazy_static;
 extern crate hyper;
 extern crate mozprofile;
 extern crate mozrunner;
 extern crate mozversion;
 extern crate regex;
-extern crate serde;
-#[macro_use]
-extern crate serde_derive;
-extern crate serde_json;
+extern crate rustc_serialize;
 extern crate uuid;
+extern crate zip;
 extern crate webdriver;
-extern crate zip;
 
 #[macro_use]
 extern crate log;
 
 use std::io::Write;
 use std::net::{IpAddr, SocketAddr};
 use std::path::PathBuf;
 use std::str::FromStr;
 
 use clap::{App, Arg};
 
 macro_rules! try_opt {
-    ($expr:expr, $err_type:expr, $err_msg:expr) => {{
+    ($expr:expr, $err_type:expr, $err_msg:expr) => ({
         match $expr {
             Some(x) => x,
-            None => return Err(WebDriverError::new($err_type, $err_msg)),
+            None => return Err(WebDriverError::new($err_type, $err_msg))
         }
-    }};
+    })
 }
 
 mod build;
-mod capabilities;
 mod logging;
-mod marionette;
 mod prefs;
-
-#[cfg(test)]
-pub mod test;
+mod marionette;
+mod capabilities;
 
 use build::BuildInfo;
-use marionette::{extension_routes, MarionetteHandler, MarionetteSettings};
+use marionette::{MarionetteHandler, MarionetteSettings, extension_routes};
 
 type ProgramResult = std::result::Result<(), (ExitCode, String)>;
 
 enum ExitCode {
     Ok = 0,
     Usage = 64,
     Unavailable = 69,
 }
@@ -64,80 +57,62 @@ fn print_version() {
     println!("");
     println!("This program is subject to the terms of the Mozilla Public License 2.0.");
     println!("You can obtain a copy of the license at https://mozilla.org/MPL/2.0/.");
 }
 
 fn app<'a, 'b>() -> App<'a, 'b> {
     App::new(format!("geckodriver {}", crate_version!()))
         .about("WebDriver implementation for Firefox.")
-        .arg(
-            Arg::with_name("webdriver_host")
-                .long("host")
-                .value_name("HOST")
-                .help("Host ip to use for WebDriver server (default: 127.0.0.1)")
-                .takes_value(true),
-        )
-        .arg(
-            Arg::with_name("webdriver_port")
-                .short("p")
-                .long("port")
-                .value_name("PORT")
-                .help("Port to use for WebDriver server (default: 4444)")
-                .takes_value(true)
-                .alias("webdriver-port"),
-        )
-        .arg(
-            Arg::with_name("binary")
-                .short("b")
-                .long("binary")
-                .value_name("BINARY")
-                .help("Path to the Firefox binary")
-                .takes_value(true),
-        )
-        .arg(
-            Arg::with_name("marionette_port")
-                .long("marionette-port")
-                .value_name("PORT")
-                .help("Port to use to connect to Gecko (default: random free port)")
-                .takes_value(true),
-        )
-        .arg(
-            Arg::with_name("connect_existing")
-                .long("connect-existing")
-                .requires("marionette_port")
-                .help("Connect to an existing Firefox instance"),
-        )
-        .arg(
-            Arg::with_name("jsdebugger")
-                .long("jsdebugger")
-                .takes_value(false)
-                .help("Attach browser toolbox debugger for Firefox"),
-        )
-        .arg(
-            Arg::with_name("verbosity")
-                .short("v")
-                .multiple(true)
-                .conflicts_with("log_level")
-                .help("Log level verbosity (-v for debug and -vv for trace level)"),
-        )
-        .arg(
-            Arg::with_name("log_level")
-                .long("log")
-                .takes_value(true)
-                .value_name("LEVEL")
-                .possible_values(&["fatal", "error", "warn", "info", "config", "debug", "trace"])
-                .help("Set Gecko log level"),
-        )
-        .arg(
-            Arg::with_name("version")
-                .short("V")
-                .long("version")
-                .help("Prints version and copying information"),
-        )
+        .arg(Arg::with_name("webdriver_host")
+            .long("host")
+            .value_name("HOST")
+            .help("Host ip to use for WebDriver server (default: 127.0.0.1)")
+            .takes_value(true))
+        .arg(Arg::with_name("webdriver_port")
+            .short("p")
+            .long("port")
+            .value_name("PORT")
+            .help("Port to use for WebDriver server (default: 4444)")
+            .takes_value(true)
+            .alias("webdriver-port"))
+        .arg(Arg::with_name("binary")
+            .short("b")
+            .long("binary")
+            .value_name("BINARY")
+            .help("Path to the Firefox binary")
+            .takes_value(true))
+        .arg(Arg::with_name("marionette_port")
+            .long("marionette-port")
+            .value_name("PORT")
+            .help("Port to use to connect to Gecko (default: random free port)")
+            .takes_value(true))
+        .arg(Arg::with_name("connect_existing")
+            .long("connect-existing")
+            .requires("marionette_port")
+            .help("Connect to an existing Firefox instance"))
+        .arg(Arg::with_name("jsdebugger")
+            .long("jsdebugger")
+            .takes_value(false)
+            .help("Attach browser toolbox debugger for Firefox"))
+        .arg(Arg::with_name("verbosity")
+            .short("v")
+            .multiple(true)
+            .conflicts_with("log_level")
+            .help("Log level verbosity (-v for debug and -vv for trace level)"))
+        .arg(Arg::with_name("log_level")
+            .long("log")
+            .takes_value(true)
+            .value_name("LEVEL")
+            .possible_values(&["fatal", "error", "warn", "info", "config", "debug", "trace"])
+            .help("Set Gecko log level"))
+        .arg(Arg::with_name("version")
+            .short("V")
+            .long("version")
+            .help("Prints version and copying information"))
 }
 
 fn run() -> ProgramResult {
     let matches = app().get_matches();
 
     if matches.is_present("version") {
         print_version();
         return Ok(());
@@ -156,20 +131,22 @@ fn run() -> ProgramResult {
     let addr = match IpAddr::from_str(host) {
         Ok(addr) => SocketAddr::new(addr, port),
         Err(_) => return Err((ExitCode::Usage, "invalid host address".into())),
     };
 
     let binary = matches.value_of("binary").map(|x| PathBuf::from(x));
 
     let marionette_port = match matches.value_of("marionette_port") {
-        Some(x) => match u16::from_str(x) {
-            Ok(x) => Some(x),
-            Err(_) => return Err((ExitCode::Usage, "invalid Marionette port".into())),
-        },
+        Some(x) => {
+            match u16::from_str(x) {
+                Ok(x) => Some(x),
+                Err(_) => return Err((ExitCode::Usage, "invalid Marionette port".into())),
+            }
+        }
         None => None,
     };
 
     let log_level = if matches.is_present("log_level") {
         logging::Level::from_str(matches.value_of("log_level").unwrap()).ok()
     } else {
         match matches.occurrences_of("verbosity") {
             0 => Some(logging::Level::Info),
--- a/testing/geckodriver/src/marionette.rs
+++ b/testing/geckodriver/src/marionette.rs
@@ -1,424 +1,466 @@
-use base64;
 use hyper::method::Method;
 use mozprofile::preferences::Pref;
 use mozprofile::profile::Profile;
-use mozrunner::runner::{FirefoxProcess, FirefoxRunner, Runner, RunnerProcess};
+use mozrunner::runner::{FirefoxRunner, FirefoxProcess, Runner, RunnerProcess};
 use regex::Captures;
-use serde::de::{self, Deserialize, Deserializer};
-use serde::ser::{Serialize, Serializer};
-use serde_json::{self, Map, Value};
+use rustc_serialize::base64::FromBase64;
+use rustc_serialize::json;
+use rustc_serialize::json::{Json, ToJson};
+use std::collections::BTreeMap;
 use std::env;
 use std::error::Error;
 use std::fs::File;
-use std::io::prelude::*;
 use std::io::Error as IoError;
 use std::io::ErrorKind;
+use std::io::prelude::*;
+use std::path::PathBuf;
 use std::io::Result as IoResult;
 use std::net::{TcpListener, TcpStream};
-use std::path::PathBuf;
 use std::sync::Mutex;
 use std::thread;
 use std::time;
 use uuid::Uuid;
 use webdriver::capabilities::CapabilitiesMatching;
-use webdriver::command::WebDriverCommand::{AcceptAlert, AddCookie, CloseWindow, DeleteCookie,
-                                           DeleteCookies, DeleteSession, DismissAlert,
-                                           ElementClear, ElementClick, ElementSendKeys,
-                                           ElementTap, ExecuteAsyncScript, ExecuteScript,
-                                           Extension, FindElement, FindElementElement,
-                                           FindElementElements, FindElements, FullscreenWindow,
-                                           Get, GetActiveElement, GetAlertText, GetCSSValue,
-                                           GetCookies, GetCurrentUrl, GetElementAttribute,
-                                           GetElementProperty, GetElementRect, GetElementTagName,
-                                           GetElementText, GetNamedCookie, GetPageSource,
-                                           GetTimeouts, GetTitle, GetWindowHandle,
-                                           GetWindowHandles, GetWindowRect, GoBack, GoForward,
-                                           IsDisplayed, IsEnabled, IsSelected, MaximizeWindow,
-                                           MinimizeWindow, NewSession, PerformActions, Refresh,
-                                           ReleaseActions, SendAlertText, SetTimeouts,
-                                           SetWindowRect, Status, SwitchToFrame,
-                                           SwitchToParentFrame, SwitchToWindow,
-                                           TakeElementScreenshot, TakeScreenshot};
-use webdriver::command::{ActionsParameters, AddCookieParameters, GetNamedCookieParameters,
-                         GetParameters, JavascriptCommandParameters, LocatorParameters,
-                         NewSessionParameters, SwitchToFrameParameters, SwitchToWindowParameters,
-                         TakeScreenshotParameters, TimeoutsParameters, WindowRectParameters};
-use webdriver::command::{WebDriverCommand, WebDriverExtensionCommand, WebDriverMessage};
-use webdriver::common::{Cookie, FrameId, WebElement, ELEMENT_KEY, FRAME_KEY, WINDOW_KEY};
-use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
-use webdriver::httpapi::WebDriverExtensionRoute;
-use webdriver::response::{CloseWindowResponse, CookieResponse, CookiesResponse,
+use webdriver::command::{WebDriverCommand, WebDriverMessage, Parameters,
+                         WebDriverExtensionCommand};
+use webdriver::command::WebDriverCommand::{
+    NewSession, DeleteSession, Status, Get, GetCurrentUrl,
+    GoBack, GoForward, Refresh, GetTitle, GetPageSource, GetWindowHandle,
+    GetWindowHandles, CloseWindow, SetWindowRect, GetWindowRect,
+    MinimizeWindow, MaximizeWindow, FullscreenWindow, SwitchToWindow, SwitchToFrame,
+    SwitchToParentFrame, FindElement, FindElements,
+    FindElementElement, FindElementElements, GetActiveElement,
+    IsDisplayed, IsSelected, GetElementAttribute, GetElementProperty, GetCSSValue,
+    GetElementText, GetElementTagName, GetElementRect, IsEnabled,
+    ElementClick, ElementTap, ElementClear, ElementSendKeys,
+    ExecuteScript, ExecuteAsyncScript, GetCookies, GetNamedCookie, AddCookie,
+    DeleteCookies, DeleteCookie, GetTimeouts, SetTimeouts, DismissAlert,
+    AcceptAlert, GetAlertText, SendAlertText, TakeScreenshot, TakeElementScreenshot,
+    Extension, PerformActions, ReleaseActions};
+use webdriver::command::{
+    NewSessionParameters, GetParameters, WindowRectParameters, SwitchToWindowParameters,
+    SwitchToFrameParameters, LocatorParameters, JavascriptCommandParameters,
+    GetNamedCookieParameters, AddCookieParameters, TimeoutsParameters,
+    ActionsParameters, TakeScreenshotParameters};
+use webdriver::response::{CloseWindowResponse, Cookie, CookieResponse, CookiesResponse,
                           ElementRectResponse, NewSessionResponse, TimeoutsResponse,
                           ValueResponse, WebDriverResponse, WindowRectResponse};
-use webdriver::server::{Session, WebDriverHandler};
+use webdriver::common::{Date, ELEMENT_KEY, FrameId, FRAME_KEY, Nullable, WebElement, WINDOW_KEY};
+use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
+use webdriver::server::{WebDriverHandler, Session};
+use webdriver::httpapi::{WebDriverExtensionRoute};
 
 use build::BuildInfo;
 use capabilities::{FirefoxCapabilities, FirefoxOptions};
 use logging;
 use prefs;
 
 // localhost may be routed to the IPv6 stack on certain systems,
 // and nsIServerSocket in Marionette only supports IPv4
 const DEFAULT_HOST: &'static str = "127.0.0.1";
 
 const CHROME_ELEMENT_KEY: &'static str = "chromeelement-9fc5-4b51-a3c8-01716eedeb04";
 const LEGACY_ELEMENT_KEY: &'static str = "ELEMENT";
 
 pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> {
-    return vec![
-        (
-            Method::Get,
-            "/session/{sessionId}/moz/context",
-            GeckoExtensionRoute::GetContext,
-        ),
-        (
-            Method::Post,
-            "/session/{sessionId}/moz/context",
-            GeckoExtensionRoute::SetContext,
-        ),
-        (
-            Method::Post,
-            "/session/{sessionId}/moz/xbl/{elementId}/anonymous_children",
-            GeckoExtensionRoute::XblAnonymousChildren,
-        ),
-        (
-            Method::Post,
-            "/session/{sessionId}/moz/xbl/{elementId}/anonymous_by_attribute",
-            GeckoExtensionRoute::XblAnonymousByAttribute,
-        ),
-        (
-            Method::Post,
-            "/session/{sessionId}/moz/addon/install",
-            GeckoExtensionRoute::InstallAddon,
-        ),
-        (
-            Method::Post,
-            "/session/{sessionId}/moz/addon/uninstall",
-            GeckoExtensionRoute::UninstallAddon,
-        ),
-    ];
+    return vec![(Method::Get, "/session/{sessionId}/moz/context", GeckoExtensionRoute::GetContext),
+             (Method::Post, "/session/{sessionId}/moz/context", GeckoExtensionRoute::SetContext),
+             (Method::Post,
+              "/session/{sessionId}/moz/xbl/{elementId}/anonymous_children",
+              GeckoExtensionRoute::XblAnonymousChildren),
+             (Method::Post,
+              "/session/{sessionId}/moz/xbl/{elementId}/anonymous_by_attribute",
+              GeckoExtensionRoute::XblAnonymousByAttribute),
+             (Method::Post, "/session/{sessionId}/moz/addon/install",
+                GeckoExtensionRoute::InstallAddon),
+             (Method::Post, "/session/{sessionId}/moz/addon/uninstall",
+                GeckoExtensionRoute::UninstallAddon)];
 }
 
 #[derive(Clone, PartialEq)]
 pub enum GeckoExtensionRoute {
     GetContext,
     SetContext,
     XblAnonymousChildren,
     XblAnonymousByAttribute,
     InstallAddon,
     UninstallAddon,
 }
 
 impl WebDriverExtensionRoute for GeckoExtensionRoute {
     type Command = GeckoExtensionCommand;
 
-    fn command(
-        &self,
-        params: &Captures,
-        body_data: &Value,
-    ) -> WebDriverResult<WebDriverCommand<GeckoExtensionCommand>> {
+    fn command(&self,
+               captures: &Captures,
+               body_data: &Json)
+               -> WebDriverResult<WebDriverCommand<GeckoExtensionCommand>> {
         let command = match self {
             &GeckoExtensionRoute::GetContext => GeckoExtensionCommand::GetContext,
             &GeckoExtensionRoute::SetContext => {
-                GeckoExtensionCommand::SetContext(serde_json::from_value(body_data.clone())?)
+                let parameters: GeckoContextParameters = try!(Parameters::from_json(&body_data));
+                GeckoExtensionCommand::SetContext(parameters)
             }
             &GeckoExtensionRoute::XblAnonymousChildren => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
-                let element = WebElement::new(element_id.as_str().to_string());
-                GeckoExtensionCommand::XblAnonymousChildren(element)
+                let element_id = try!(captures.name("elementId")
+                    .ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                               "Missing elementId parameter")));
+                GeckoExtensionCommand::XblAnonymousChildren(element_id.as_str().into())
             }
             &GeckoExtensionRoute::XblAnonymousByAttribute => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
-                GeckoExtensionCommand::XblAnonymousByAttribute(
-                    WebElement::new(element_id.as_str().into()),
-                    serde_json::from_value(body_data.clone())?,
-                )
+                let element_id = try!(captures.name("elementId")
+                    .ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                               "Missing elementId parameter")));
+                let parameters: AttributeParameters = try!(Parameters::from_json(&body_data));
+                GeckoExtensionCommand::XblAnonymousByAttribute(element_id.as_str().into(),
+                                                               parameters)
             }
             &GeckoExtensionRoute::InstallAddon => {
-                GeckoExtensionCommand::InstallAddon(serde_json::from_value(body_data.clone())?)
+                let parameters: AddonInstallParameters = try!(Parameters::from_json(&body_data));
+                GeckoExtensionCommand::InstallAddon(parameters)
             }
             &GeckoExtensionRoute::UninstallAddon => {
-                GeckoExtensionCommand::UninstallAddon(serde_json::from_value(body_data.clone())?)
+                let parameters: AddonUninstallParameters = try!(Parameters::from_json(&body_data));
+                GeckoExtensionCommand::UninstallAddon(parameters)
             }
         };
         Ok(WebDriverCommand::Extension(command))
     }
 }
 
 #[derive(Clone, PartialEq)]
 pub enum GeckoExtensionCommand {
     GetContext,
     SetContext(GeckoContextParameters),
     XblAnonymousChildren(WebElement),
-    XblAnonymousByAttribute(WebElement, XblLocatorParameters),
+    XblAnonymousByAttribute(WebElement, AttributeParameters),
     InstallAddon(AddonInstallParameters),
-    UninstallAddon(AddonUninstallParameters),
+    UninstallAddon(AddonUninstallParameters)
 }
 
 impl WebDriverExtensionCommand for GeckoExtensionCommand {
-    fn parameters_json(&self) -> Option<Value> {
+    fn parameters_json(&self) -> Option<Json> {
         match self {
             &GeckoExtensionCommand::GetContext => None,
-            &GeckoExtensionCommand::InstallAddon(ref x) => {
-                Some(serde_json::to_value(x.clone()).unwrap())
-            }
-            &GeckoExtensionCommand::SetContext(ref x) => {
-                Some(serde_json::to_value(x.clone()).unwrap())
-            }
-            &GeckoExtensionCommand::UninstallAddon(ref x) => {
-                Some(serde_json::to_value(x.clone()).unwrap())
-            }
-            &GeckoExtensionCommand::XblAnonymousByAttribute(_, ref x) => {
-                Some(serde_json::to_value(x.clone()).unwrap())
-            }
+            &GeckoExtensionCommand::SetContext(ref x) => Some(x.to_json()),
             &GeckoExtensionCommand::XblAnonymousChildren(_) => None,
+            &GeckoExtensionCommand::XblAnonymousByAttribute(_, ref x) => Some(x.to_json()),
+            &GeckoExtensionCommand::InstallAddon(ref x) => Some(x.to_json()),
+            &GeckoExtensionCommand::UninstallAddon(ref x) => Some(x.to_json()),
         }
     }
 }
 
-#[derive(Clone, Debug, PartialEq, Serialize)]
-pub struct AddonInstallParameters {
-    pub path: String,
-    pub temporary: bool,
-}
-
-impl<'de> Deserialize<'de> for AddonInstallParameters {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        #[derive(Debug, Deserialize)]
-        #[serde(deny_unknown_fields)]
-        struct Base64 {
-            addon: String,
-            temporary: bool,
-        };
-
-        #[derive(Debug, Deserialize)]
-        #[serde(deny_unknown_fields)]
-        struct Path {
-            path: String,
-            temporary: bool,
-        };
-
-        #[derive(Debug, Deserialize)]
-        #[serde(untagged)]
-        enum Helper {
-            Base64(Base64),
-            Path(Path),
-        };
-
-        let params = match Helper::deserialize(deserializer)? {
-            Helper::Path(ref mut data) => AddonInstallParameters {
-                path: data.path.clone(),
-                temporary: data.temporary,
-            },
-            Helper::Base64(ref mut data) => {
-                let content = base64::decode(&data.addon).map_err(de::Error::custom)?;
-
-                let path = env::temp_dir()
-                    .as_path()
-                    .join(format!("addon-{}.xpi", Uuid::new_v4()));
-                let mut xpi_file = File::create(&path).map_err(de::Error::custom)?;
-                xpi_file
-                    .write(content.as_slice())
-                    .map_err(de::Error::custom)?;
-
-                let path = match path.to_str() {
-                    Some(path) => path.to_string(),
-                    None => return Err(de::Error::custom("could not write addon to file")),
-                };
-
-                AddonInstallParameters {
-                    path: path,
-                    temporary: data.temporary,
-                }
-            }
-        };
-
-        Ok(params)
-    }
-}
-
-impl ToMarionette for AddonInstallParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut data = Map::new();
-        data.insert("path".to_string(), Value::String(self.path.clone()));
-        data.insert("temporary".to_string(), Value::Bool(self.temporary));
-        Ok(data)
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct AddonUninstallParameters {
-    pub id: String,
-}
-
-impl ToMarionette for AddonUninstallParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut data = Map::new();
-        data.insert("id".to_string(), Value::String(self.id.clone()));
-        Ok(data)
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "lowercase")]
+#[derive(Clone, Debug, PartialEq)]
 enum GeckoContext {
     Content,
     Chrome,
 }
 
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+impl ToJson for GeckoContext {
+    fn to_json(&self) -> Json {
+        match self {
+            &GeckoContext::Content => Json::String("content".to_owned()),
+            &GeckoContext::Chrome => Json::String("chrome".to_owned()),
+        }
+    }
+}
+
+#[derive(Clone, Debug, PartialEq)]
 pub struct GeckoContextParameters {
-    context: GeckoContext,
+    context: GeckoContext
+}
+
+impl Parameters for GeckoContextParameters {
+    fn from_json(body: &Json) -> WebDriverResult<GeckoContextParameters> {
+        let data = try!(body.as_object().ok_or(
+            WebDriverError::new(ErrorStatus::InvalidArgument,
+                                "Message body was not an object")));
+        let context_value = try!(data.get("context").ok_or(
+            WebDriverError::new(ErrorStatus::InvalidArgument,
+                                "Missing context key")));
+        let value = try!(context_value.as_string().ok_or(
+            WebDriverError::new(
+                ErrorStatus::InvalidArgument,
+                "context was not a string")));
+        let context = try!(match value {
+            "chrome" => Ok(GeckoContext::Chrome),
+            "content" => Ok(GeckoContext::Content),
+            _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                         format!("{} is not a valid context",
+                                                 value)))
+        });
+        Ok(GeckoContextParameters {
+            context: context
+        })
+    }
 }
 
 impl ToMarionette for GeckoContextParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut data = Map::new();
-        data.insert(
-            "value".to_owned(),
-            serde_json::to_value(self.context.clone())?,
-        );
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        let mut data = BTreeMap::new();
+        data.insert("value".to_owned(), self.context.to_json());
+        Ok(data)
+    }
+}
+
+impl ToJson for GeckoContextParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("context".to_owned(), self.context.to_json());
+        Json::Object(data)
+    }
+}
+
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct AttributeParameters {
+    name: String,
+    value: String
+}
+
+impl Parameters for AttributeParameters {
+    fn from_json(body: &Json) -> WebDriverResult<AttributeParameters> {
+        let data = try!(body.as_object().ok_or(
+            WebDriverError::new(ErrorStatus::InvalidArgument,
+                                "Message body was not an object")));
+        let name = try!(try!(data.get("name").ok_or(
+            WebDriverError::new(ErrorStatus::InvalidArgument,
+                                "Missing 'name' parameter"))).as_string().
+                            ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                      "'name' parameter is not a string")));
+        let value = try!(try!(data.get("value").ok_or(
+            WebDriverError::new(ErrorStatus::InvalidArgument,
+                                "Missing 'value' parameter"))).as_string().
+                            ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                      "'value' parameter is not a string")));
+        Ok(AttributeParameters {
+            name: name.to_owned(),
+            value: value.to_owned(),
+        })
+    }
+}
+
+impl ToJson for AttributeParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("name".to_owned(), self.name.to_json());
+        data.insert("value".to_owned(), self.value.to_json());
+        Json::Object(data)
+    }
+}
+
+impl ToMarionette for AttributeParameters {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        let mut data = BTreeMap::new();
+        data.insert("using".to_owned(), "anon attribute".to_json());
+        let mut value = BTreeMap::new();
+        value.insert(self.name.to_owned(), self.value.to_json());
+        data.insert("value".to_owned(), Json::Object(value));
         Ok(data)
     }
 }
 
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct XblLocatorParameters {
-    name: String,
-    value: String,
+#[derive(Clone, Debug, PartialEq)]
+pub struct AddonInstallParameters {
+    pub path: String,
+    pub temporary: bool
 }
 
-impl ToMarionette for XblLocatorParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut value = Map::new();
-        value.insert(self.name.to_owned(), Value::String(self.value.clone()));
+impl Parameters for AddonInstallParameters {
+    fn from_json(body: &Json) -> WebDriverResult<AddonInstallParameters> {
+        let data = try!(body.as_object().ok_or(
+            WebDriverError::new(ErrorStatus::InvalidArgument,
+                                "Message body was not an object")));
+
+        let base64 = match data.get("addon") {
+            Some(x) => {
+                let s = try_opt!(x.as_string(),
+                                 ErrorStatus::InvalidArgument,
+                                 "'addon' is not a string").to_string();
+
+                let addon_path = env::temp_dir().as_path()
+                    .join(format!("addon-{}.xpi", Uuid::new_v4()));
+                let mut addon_file = try!(File::create(&addon_path));
+                let addon_buf = try!(s.from_base64());
+                try!(addon_file.write(addon_buf.as_slice()));
 
-        let mut data = Map::new();
-        data.insert(
-            "using".to_owned(),
-            Value::String("anon attribute".to_string()),
-        );
-        data.insert("value".to_owned(), Value::Object(value));
+                Some(try_opt!(addon_path.to_str(),
+                              ErrorStatus::UnknownError,
+                              "could not write addon to file").to_string())
+            },
+            None => None,
+        };
+        let path = match data.get("path") {
+            Some(x) => Some(try_opt!(x.as_string(),
+                                     ErrorStatus::InvalidArgument,
+                                     "'path' is not a string").to_string()),
+            None => None,
+        };
+        if (base64.is_none() && path.is_none()) || (base64.is_some() && path.is_some()) {
+            return Err(WebDriverError::new(
+                ErrorStatus::InvalidArgument,
+                "Must specify exactly one of 'path' and 'addon'"));
+        }
+
+        let temporary = match data.get("temporary") {
+            Some(x) => try_opt!(x.as_boolean(),
+                                ErrorStatus::InvalidArgument,
+                                "Failed to convert 'temporary' to boolean"),
+            None => false
+        };
+
+        return Ok(AddonInstallParameters {
+            path: base64.or(path).unwrap(),
+            temporary: temporary,
+        })
+    }
+}
+
+impl ToJson for AddonInstallParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("path".to_string(), self.path.to_json());
+        data.insert("temporary".to_string(), self.temporary.to_json());
+        Json::Object(data)
+    }
+}
+
+impl ToMarionette for AddonInstallParameters {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        let mut data = BTreeMap::new();
+        data.insert("path".to_string(), self.path.to_json());
+        data.insert("temporary".to_string(), self.temporary.to_json());
         Ok(data)
     }
 }
 
-#[derive(Default, Debug)]
-pub struct LogOptions {
-    pub level: Option<logging::Level>,
+#[derive(Clone, Debug, PartialEq)]
+pub struct AddonUninstallParameters {
+    pub id: String
 }
 
-#[derive(Debug, PartialEq, Deserialize)]
-pub struct MarionetteHandshake {
-    #[serde(rename = "marionetteProtocol")]
-    protocol: u16,
-    #[serde(rename = "applicationType")]
-    application_type: String,
+impl Parameters for AddonUninstallParameters {
+    fn from_json(body: &Json) -> WebDriverResult<AddonUninstallParameters> {
+        let data = try!(body.as_object().ok_or(
+            WebDriverError::new(ErrorStatus::InvalidArgument,
+                                "Message body was not an object")));
+
+        let id = try_opt!(
+            try_opt!(data.get("id"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'id' parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "'id' is not a string").to_string();
+
+        return Ok(AddonUninstallParameters {id: id})
+    }
+}
+
+impl ToJson for AddonUninstallParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("id".to_string(), self.id.to_json());
+        Json::Object(data)
+    }
+}
+
+impl ToMarionette for AddonUninstallParameters {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        let mut data = BTreeMap::new();
+        data.insert("id".to_string(), self.id.to_json());
+        Ok(data)
+    }
+}
+
+#[derive(Default)]
+pub struct LogOptions {
+    pub level: Option<logging::Level>,
 }
 
 #[derive(Default)]
 pub struct MarionetteSettings {
     pub port: Option<u16>,
     pub binary: Option<PathBuf>,
     pub connect_existing: bool,
 
     /// Brings up the Browser Toolbox when starting Firefox,
     /// letting you debug internals.
     pub jsdebugger: bool,
 }
 
-#[derive(Default)]
 pub struct MarionetteHandler {
     connection: Mutex<Option<MarionetteConnection>>,
     settings: MarionetteSettings,
     browser: Option<FirefoxProcess>,
 }
 
 impl MarionetteHandler {
     pub fn new(settings: MarionetteSettings) -> MarionetteHandler {
         MarionetteHandler {
             connection: Mutex::new(None),
             settings,
             browser: None,
         }
     }
 
-    fn create_connection(
-        &mut self,
-        session_id: &Option<String>,
-        new_session_parameters: &NewSessionParameters,
-    ) -> WebDriverResult<Map<String, Value>> {
+    fn create_connection(&mut self,
+                         session_id: &Option<String>,
+                         new_session_parameters: &NewSessionParameters)
+                         -> WebDriverResult<BTreeMap<String, Json>> {
         let (options, capabilities) = {
             let mut fx_capabilities = FirefoxCapabilities::new(self.settings.binary.as_ref());
             let mut capabilities = try!(
-                try!(new_session_parameters.match_browser(&mut fx_capabilities)).ok_or(
-                    WebDriverError::new(
+                try!(new_session_parameters
+                    .match_browser(&mut fx_capabilities))
+                    .ok_or(WebDriverError::new(
                         ErrorStatus::SessionNotCreated,
-                        "Unable to find a matching set of capabilities",
-                    ),
-                )
-            );
+                        "Unable to find a matching set of capabilities")));
 
-            let options = try!(FirefoxOptions::from_capabilities(
-                fx_capabilities.chosen_binary,
-                &mut capabilities
-            ));
+            let options = try!(FirefoxOptions::from_capabilities(fx_capabilities.chosen_binary,
+                                                                 &mut capabilities));
             (options, capabilities)
         };
 
         if let Some(l) = options.log.level {
             logging::set_max_level(l);
         }
 
         let port = self.settings.port.unwrap_or(get_free_port()?);
         if !self.settings.connect_existing {
             try!(self.start_browser(port, options));
         }
 
         let mut connection = MarionetteConnection::new(port, session_id.clone());
-        connection.connect(&mut self.browser)?;
+        try!(connection.connect(&mut self.browser));
         self.connection = Mutex::new(Some(connection));
+
         Ok(capabilities)
     }
 
     fn start_browser(&mut self, port: u16, options: FirefoxOptions) -> WebDriverResult<()> {
-        let binary = options.binary.ok_or(WebDriverError::new(
-            ErrorStatus::SessionNotCreated,
-            "Expected browser binary location, but unable to find \
-             binary in default location, no \
-             'moz:firefoxOptions.binary' capability provided, and \
-             no binary flag set on the command line",
-        ))?;
+        let binary = options.binary
+            .ok_or(WebDriverError::new(ErrorStatus::SessionNotCreated,
+                                       "Expected browser binary location, but unable to find \
+                                        binary in default location, no \
+                                        'moz:firefoxOptions.binary' capability provided, and \
+                                        no binary flag set on the command line"))?;
 
         let is_custom_profile = options.profile.is_some();
 
         let mut profile = match options.profile {
             Some(x) => x,
-            None => Profile::new(None)?,
+            None => Profile::new(None)?
         };
 
         self.set_prefs(port, &mut profile, is_custom_profile, options.prefs)
             .map_err(|e| {
-                WebDriverError::new(
-                    ErrorStatus::SessionNotCreated,
-                    format!("Failed to set preferences: {}", e),
-                )
+                WebDriverError::new(ErrorStatus::SessionNotCreated,
+                                    format!("Failed to set preferences: {}", e))
             })?;
 
         let mut runner = FirefoxRunner::new(&binary, profile);
 
         // https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting
         runner
             .env("MOZ_CRASHREPORTER", "1")
             .env("MOZ_CRASHREPORTER_NO_REPORT", "1")
@@ -428,22 +470,22 @@ impl MarionetteHandler {
         runner.arg("-marionette");
         if self.settings.jsdebugger {
             runner.arg("-jsdebugger");
         }
         if let Some(args) = options.args.as_ref() {
             runner.args(args);
         }
 
-        let browser_proc = runner.start().map_err(|e| {
-            WebDriverError::new(
-                ErrorStatus::SessionNotCreated,
-                format!("Failed to start browser {}: {}", binary.display(), e),
-            )
-        })?;
+        let browser_proc = runner.start()
+            .map_err(|e| {
+                WebDriverError::new(ErrorStatus::SessionNotCreated,
+                                    format!("Failed to start browser {}: {}",
+                                            binary.display(), e))
+            })?;
         self.browser = Some(browser_proc);
 
         Ok(())
     }
 
     pub fn set_prefs(
         &self,
         port: u16,
@@ -462,117 +504,100 @@ impl MarionetteHandler {
             if !custom_profile || !prefs.contains_key(name) {
                 prefs.insert((*name).clone(), (*value).clone());
             }
         }
 
         prefs.insert_slice(&extra_prefs[..]);
 
         if self.settings.jsdebugger {
-            prefs.insert(
-                "devtools.browsertoolbox.panel",
-                Pref::new("jsdebugger".to_owned()),
-            );
+            prefs.insert("devtools.browsertoolbox.panel", Pref::new("jsdebugger".to_owned()));
             prefs.insert("devtools.debugger.remote-enabled", Pref::new(true));
             prefs.insert("devtools.chrome.enabled", Pref::new(true));
             prefs.insert("devtools.debugger.prompt-connection", Pref::new(false));
             prefs.insert("marionette.debugging.clicktostart", Pref::new(true));
         }
 
-        prefs.insert(
-            "marionette.log.level",
-            Pref::new(logging::max_level().to_string()),
-        );
+        prefs.insert("marionette.log.level", logging::max_level().into());
         prefs.insert("marionette.port", Pref::new(port));
 
         prefs.write().map_err(|_| {
             WebDriverError::new(ErrorStatus::UnknownError, "Unable to write Firefox profile")
         })
     }
 }
 
 impl WebDriverHandler<GeckoExtensionRoute> for MarionetteHandler {
-    fn handle_command(
-        &mut self,
-        _: &Option<Session>,
-        msg: WebDriverMessage<GeckoExtensionRoute>,
-    ) -> WebDriverResult<WebDriverResponse> {
+    fn handle_command(&mut self, _: &Option<Session>,
+                      msg: WebDriverMessage<GeckoExtensionRoute>) -> WebDriverResult<WebDriverResponse> {
         let mut resolved_capabilities = None;
         {
             let mut capabilities_options = None;
             // First handle the status message which doesn't actually require a marionette
             // connection or message
             if msg.command == Status {
-                let (ready, message) = self.connection
-                    .lock()
-                    .map(|ref connection| {
-                        connection
-                            .as_ref()
-                            .map(|_| (false, "Session already started"))
-                            .unwrap_or((true, ""))
-                    })
+                let (ready, message) = self.connection.lock()
+                    .map(|ref connection| connection
+                         .as_ref()
+                         .map(|_| (false, "Session already started"))
+                         .unwrap_or((true, "")))
                     .unwrap_or((false, "geckodriver internal error"));
-                let mut value = Map::new();
-                value.insert("ready".to_string(), Value::Bool(ready));
-                value.insert("message".to_string(), Value::String(message.into()));
-                return Ok(WebDriverResponse::Generic(ValueResponse(Value::Object(
-                    value,
-                ))));
+                let mut value = BTreeMap::new();
+                value.insert("ready".to_string(), Json::Boolean(ready));
+                value.insert("message".to_string(), Json::String(message.into()));
+                return Ok(WebDriverResponse::Generic(ValueResponse::new(Json::Object(value))));
             }
-
             match self.connection.lock() {
                 Ok(ref connection) => {
                     if connection.is_none() {
                         match msg.command {
                             NewSession(ref capabilities) => {
                                 capabilities_options = Some(capabilities);
-                            }
+                            },
                             _ => {
                                 return Err(WebDriverError::new(
                                     ErrorStatus::InvalidSessionId,
-                                    "Tried to run command without establishing a connection",
-                                ));
+                                    "Tried to run command without establishing a connection"));
                             }
                         }
                     }
-                }
+                },
                 Err(_) => {
                     return Err(WebDriverError::new(
                         ErrorStatus::UnknownError,
-                        "Failed to aquire Marionette connection",
-                    ))
+                        "Failed to aquire Marionette connection"))
                 }
             }
             if let Some(capabilities) = capabilities_options {
-                resolved_capabilities =
-                    Some(try!(self.create_connection(&msg.session_id, &capabilities)));
+                resolved_capabilities = Some(try!(
+                    self.create_connection(&msg.session_id, &capabilities)));
             }
         }
 
         match self.connection.lock() {
             Ok(ref mut connection) => {
                 match connection.as_mut() {
                     Some(conn) => {
                         conn.send_command(resolved_capabilities, &msg)
                             .map_err(|mut err| {
                                 // Shutdown the browser if no session can
                                 // be established due to errors.
                                 if let NewSession(_) = msg.command {
                                     err.delete_session = true;
                                 }
-                                err
-                            })
-                    }
-                    None => panic!("Connection missing"),
+                                err})
+                    },
+                    None => panic!("Connection missing")
                 }
+            },
+            Err(_) => {
+                Err(WebDriverError::new(
+                    ErrorStatus::UnknownError,
+                    "Failed to aquire Marionette connection"))
             }
-            Err(_) => Err(WebDriverError::new(
-                ErrorStatus::UnknownError,
-                "Failed to aquire Marionette connection",
-            )),
         }
     }
 
     fn delete_session(&mut self, session: &Option<Session>) {
         if let Some(ref s) = *session {
             let delete_session = WebDriverMessage {
                 session_id: Some(s.id.clone()),
                 command: WebDriverCommand::DeleteSession,
@@ -597,60 +622,54 @@ impl WebDriverHandler<GeckoExtensionRout
 
         self.connection = Mutex::new(None);
         self.browser = None;
     }
 }
 
 pub struct MarionetteSession {
     pub session_id: String,
-    protocol: Option<u16>,
+    protocol: Option<String>,
     application_type: Option<String>,
-    command_id: u64,
+    command_id: u64
 }
 
 impl MarionetteSession {
     pub fn new(session_id: Option<String>) -> MarionetteSession {
         let initital_id = session_id.unwrap_or("".to_string());
         MarionetteSession {
             session_id: initital_id,
             protocol: None,
             application_type: None,
-            command_id: 0,
+            command_id: 0
         }
     }
 
-    pub fn update(
-        &mut self,
-        msg: &WebDriverMessage<GeckoExtensionRoute>,
-        resp: &MarionetteResponse,
-    ) -> WebDriverResult<()> {
+    pub fn update(&mut self, msg: &WebDriverMessage<GeckoExtensionRoute>,
+                  resp: &MarionetteResponse) -> WebDriverResult<()> {
         match msg.command {
             NewSession(_) => {
                 let session_id = try_opt!(
-                    try_opt!(
-                        resp.result.get("sessionId"),
+                    try_opt!(resp.result.find("sessionId"),
+                             ErrorStatus::SessionNotCreated,
+                             "Unable to get session id").as_string(),
                         ErrorStatus::SessionNotCreated,
-                        "Unable to get session id"
-                    ).as_str(),
-                    ErrorStatus::SessionNotCreated,
-                    "Unable to convert session id to string"
-                );
+                        "Unable to convert session id to string");
                 self.session_id = session_id.to_string().clone();
-            }
+            },
             _ => {}
         }
         Ok(())
     }
 
     /// Converts a Marionette JSON response into a `WebElement`.
     ///
     /// Note that it currently coerces all chrome elements, web frames, and web
     /// windows also into web elements.  This will change at a later point.
-    fn to_web_element(&self, json_data: &Value) -> WebDriverResult<WebElement> {
+    fn to_web_element(&self, json_data: &Json) -> WebDriverResult<WebElement> {
         let data = try_opt!(
             json_data.as_object(),
             ErrorStatus::UnknownError,
             "Failed to convert data to an object"
         );
 
         let chrome_element = data.get(CHROME_ELEMENT_KEY);
         let element = data.get(ELEMENT_KEY);
@@ -663,472 +682,435 @@ impl MarionetteSession {
                 .or(legacy_element)
                 .or(chrome_element)
                 .or(frame)
                 .or(window),
             ErrorStatus::UnknownError,
             "Failed to extract web element from Marionette response"
         );
         let id = try_opt!(
-            value.as_str(),
+            value.as_string(),
             ErrorStatus::UnknownError,
             "Failed to convert web element reference value to string"
         ).to_string();
         Ok(WebElement::new(id))
     }
 
     pub fn next_command_id(&mut self) -> u64 {
         self.command_id = self.command_id + 1;
         self.command_id
     }
 
-    pub fn response(
-        &mut self,
-        msg: &WebDriverMessage<GeckoExtensionRoute>,
-        resp: MarionetteResponse,
-    ) -> WebDriverResult<WebDriverResponse> {
+    pub fn response(&mut self, msg: &WebDriverMessage<GeckoExtensionRoute>,
+                    resp: MarionetteResponse) -> WebDriverResult<WebDriverResponse> {
+
         if resp.id != self.command_id {
-            return Err(WebDriverError::new(
-                ErrorStatus::UnknownError,
-                format!(
-                    "Marionette responses arrived out of sequence, expected {}, got {}",
-                    self.command_id, resp.id
-                ),
-            ));
+            return Err(WebDriverError::new(ErrorStatus::UnknownError,
+                                           format!("Marionette responses arrived out of sequence, expected {}, got {}",
+                                                   self.command_id, resp.id)));
         }
 
         if let Some(error) = resp.error {
             return Err(error.into());
         }
 
         try!(self.update(msg, &resp));
 
         Ok(match msg.command {
             // Everything that doesn't have a response value
-            Get(_)
-            | GoBack
-            | GoForward
-            | Refresh
-            | SetTimeouts(_)
-            | SwitchToWindow(_)
-            | SwitchToFrame(_)
-            | SwitchToParentFrame
-            | AddCookie(_)
-            | DeleteCookies
-            | DeleteCookie(_)
-            | DismissAlert
-            | AcceptAlert
-            | SendAlertText(_)
-            | ElementClick(_)
-            | ElementTap(_)
-            | ElementClear(_)
-            | ElementSendKeys(_, _)
-            | PerformActions(_)
-            | ReleaseActions => WebDriverResponse::Void,
+            Get(_) | GoBack | GoForward | Refresh | SetTimeouts(_) |
+            SwitchToWindow(_) | SwitchToFrame(_) |
+            SwitchToParentFrame | AddCookie(_) | DeleteCookies | DeleteCookie(_) |
+            DismissAlert | AcceptAlert | SendAlertText(_) | ElementClick(_) |
+            ElementTap(_) | ElementClear(_) | ElementSendKeys(_, _) |
+            PerformActions(_) | ReleaseActions => {
+                WebDriverResponse::Void
+            },
             // Things that simply return the contents of the marionette "value" property
-            GetCurrentUrl
-            | GetTitle
-            | GetPageSource
-            | GetWindowHandle
-            | IsDisplayed(_)
-            | IsSelected(_)
-            | GetElementAttribute(_, _)
-            | GetElementProperty(_, _)
-            | GetCSSValue(_, _)
-            | GetElementText(_)
-            | GetElementTagName(_)
-            | IsEnabled(_)
-            | ExecuteScript(_)
-            | ExecuteAsyncScript(_)
-            | GetAlertText
-            | TakeScreenshot
-            | TakeElementScreenshot(_) => WebDriverResponse::Generic(resp.to_value_response(true)?),
+            GetCurrentUrl | GetTitle | GetPageSource | GetWindowHandle | IsDisplayed(_) |
+            IsSelected(_) | GetElementAttribute(_, _) | GetElementProperty(_, _) |
+            GetCSSValue(_, _) | GetElementText(_) |
+            GetElementTagName(_) | IsEnabled(_) | ExecuteScript(_) | ExecuteAsyncScript(_) |
+            GetAlertText | TakeScreenshot | TakeElementScreenshot(_) => {
+                let value = try_opt!(resp.result.find("value"),
+                                     ErrorStatus::UnknownError,
+                                     "Failed to find value field");
+                //TODO: Convert webelement keys
+                WebDriverResponse::Generic(ValueResponse::new(value.clone()))
+            },
             GetTimeouts => {
-                let script = try_opt!(
-                    try_opt!(
-                        resp.result.get("script"),
-                        ErrorStatus::UnknownError,
-                        "Missing field: script"
-                    ).as_u64(),
-                    ErrorStatus::UnknownError,
-                    "Failed to interpret script timeout duration as u64"
-                );
+                let script = try_opt!(try_opt!(resp.result
+                                                   .find("script"),
+                                               ErrorStatus::UnknownError,
+                                               "Missing field: script")
+                                          .as_u64(),
+                                      ErrorStatus::UnknownError,
+                                      "Failed to interpret script timeout duration as u64");
                 // Check for the spec-compliant "pageLoad", but also for "page load",
                 // which was sent by Firefox 52 and earlier.
-                let page_load = try_opt!(
-                    try_opt!(
-                        resp.result.get("pageLoad").or(resp.result.get("page load")),
-                        ErrorStatus::UnknownError,
-                        "Missing field: pageLoad"
-                    ).as_u64(),
-                    ErrorStatus::UnknownError,
-                    "Failed to interpret page load duration as u64"
-                );
-                let implicit = try_opt!(
-                    try_opt!(
-                        resp.result.get("implicit"),
-                        ErrorStatus::UnknownError,
-                        "Missing field: implicit"
-                    ).as_u64(),
-                    ErrorStatus::UnknownError,
-                    "Failed to interpret implicit search duration as u64"
-                );
+                let page_load = try_opt!(try_opt!(resp.result.find("pageLoad")
+                                                      .or(resp.result.find("page load")),
+                                                  ErrorStatus::UnknownError,
+                                                  "Missing field: pageLoad")
+                                             .as_u64(),
+                                         ErrorStatus::UnknownError,
+                                         "Failed to interpret page load duration as u64");
+                let implicit = try_opt!(try_opt!(resp.result
+                                                     .find("implicit"),
+                                                 ErrorStatus::UnknownError,
+                                                 "Missing field: implicit")
+                                            .as_u64(),
+                                        ErrorStatus::UnknownError,
+                                        "Failed to interpret implicit search duration as u64");
 
                 WebDriverResponse::Timeouts(TimeoutsResponse {
                     script: script,
                     pageLoad: page_load,
                     implicit: implicit,
                 })
-            }
+            },
             Status => panic!("Got status command that should already have been handled"),
-            GetWindowHandles => WebDriverResponse::Generic(resp.to_value_response(false)?),
+            GetWindowHandles => {
+                WebDriverResponse::Generic(ValueResponse::new(resp.result.clone()))
+            },
             CloseWindow => {
-                let data = try_opt!(
-                    resp.result.as_array(),
-                    ErrorStatus::UnknownError,
-                    "Failed to interpret value as array"
-                );
-                let handles = try!(
-                    data.iter()
-                        .map(|x| {
-                            Ok(try_opt!(
-                                x.as_str(),
-                                ErrorStatus::UnknownError,
-                                "Failed to interpret window handle as string"
-                            ).to_owned())
-                        })
-                        .collect()
-                );
-                WebDriverResponse::CloseWindow(CloseWindowResponse(handles))
-            }
+                let data = try_opt!(resp.result.as_array(),
+                                    ErrorStatus::UnknownError,
+                                    "Failed to interpret value as array");
+                let handles = try!(data.iter()
+                                       .map(|x| {
+                                                Ok(try_opt!(x.as_string(),
+                                                            ErrorStatus::UnknownError,
+                                                            "Failed to interpret window handle as string")
+                                                           .to_owned())
+                                            })
+                                       .collect());
+                WebDriverResponse::CloseWindow(CloseWindowResponse { window_handles: handles })
+            },
             GetElementRect(_) => {
                 let x = try_opt!(
-                    try_opt!(
-                        resp.result.get("x"),
-                        ErrorStatus::UnknownError,
-                        "Failed to find x field"
-                    ).as_f64(),
+                    try_opt!(resp.result.find("x"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find x field").as_f64(),
                     ErrorStatus::UnknownError,
-                    "Failed to interpret x as float"
-                );
+                    "Failed to interpret x as float");
 
                 let y = try_opt!(
-                    try_opt!(
-                        resp.result.get("y"),
-                        ErrorStatus::UnknownError,
-                        "Failed to find y field"
-                    ).as_f64(),
+                    try_opt!(resp.result.find("y"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find y field").as_f64(),
                     ErrorStatus::UnknownError,
-                    "Failed to interpret y as float"
-                );
+                    "Failed to interpret y as float");
 
                 let width = try_opt!(
-                    try_opt!(
-                        resp.result.get("width"),
-                        ErrorStatus::UnknownError,
-                        "Failed to find width field"
-                    ).as_f64(),
+                    try_opt!(resp.result.find("width"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find width field").as_f64(),
                     ErrorStatus::UnknownError,
-                    "Failed to interpret width as float"
-                );
+                    "Failed to interpret width as float");
 
                 let height = try_opt!(
-                    try_opt!(
-                        resp.result.get("height"),
-                        ErrorStatus::UnknownError,
-                        "Failed to find height field"
-                    ).as_f64(),
+                    try_opt!(resp.result.find("height"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find height field").as_f64(),
                     ErrorStatus::UnknownError,
-                    "Failed to interpret width as float"
-                );
+                    "Failed to interpret width as float");
 
-                let rect = ElementRectResponse {
-                    x,
-                    y,
-                    width,
-                    height,
-                };
+                let rect = ElementRectResponse { x, y, width, height };
                 WebDriverResponse::ElementRect(rect)
-            }
-            FullscreenWindow | MinimizeWindow | MaximizeWindow | GetWindowRect
-            | SetWindowRect(_) => {
+            },
+            FullscreenWindow | MinimizeWindow | MaximizeWindow | GetWindowRect |
+            SetWindowRect(_) => {
                 let width = try_opt!(
-                    try_opt!(
-                        resp.result.get("width"),
-                        ErrorStatus::UnknownError,
-                        "Failed to find width field"
-                    ).as_u64(),
+                    try_opt!(resp.result.find("width"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find width field").as_u64(),
                     ErrorStatus::UnknownError,
-                    "Failed to interpret width as positive integer"
-                );
+                    "Failed to interpret width as positive integer");
 
                 let height = try_opt!(
-                    try_opt!(
-                        resp.result.get("height"),
-                        ErrorStatus::UnknownError,
-                        "Failed to find heigenht field"
-                    ).as_u64(),
+                    try_opt!(resp.result.find("height"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find heigenht field").as_u64(),
                     ErrorStatus::UnknownError,
-                    "Failed to interpret height as positive integer"
-                );
+                    "Failed to interpret height as positive integer");
 
                 let x = try_opt!(
-                    try_opt!(
-                        resp.result.get("x"),
-                        ErrorStatus::UnknownError,
-                        "Failed to find x field"
-                    ).as_i64(),
+                    try_opt!(resp.result.find("x"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find x field").as_i64(),
                     ErrorStatus::UnknownError,
-                    "Failed to interpret x as integer"
-                );
+                    "Failed to interpret x as integer");
 
                 let y = try_opt!(
-                    try_opt!(
-                        resp.result.get("y"),
-                        ErrorStatus::UnknownError,
-                        "Failed to find y field"
-                    ).as_i64(),
+                    try_opt!(resp.result.find("y"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find y field").as_i64(),
                     ErrorStatus::UnknownError,
-                    "Failed to interpret y as integer"
-                );
+                    "Failed to interpret y as integer");
 
                 let rect = WindowRectResponse {
                     x: x as i32,
                     y: y as i32,
                     width: width as i32,
                     height: height as i32,
                 };
                 WebDriverResponse::WindowRect(rect)
-            }
+            },
             GetCookies => {
-                let cookies: Vec<Cookie> = serde_json::from_value(resp.result)?;
-                WebDriverResponse::Cookies(CookiesResponse(cookies))
-            }
+                let cookies = try!(self.process_cookies(&resp.result));
+                WebDriverResponse::Cookies(CookiesResponse { value: cookies })
+            },
             GetNamedCookie(ref name) => {
-                let mut cookies: Vec<Cookie> = serde_json::from_value(resp.result)?;
+                let mut cookies = try!(self.process_cookies(&resp.result));
                 cookies.retain(|x| x.name == *name);
-                let cookie = try_opt!(
-                    cookies.pop(),
-                    ErrorStatus::NoSuchCookie,
-                    format!("No cookie with name {}", name)
-                );
-                WebDriverResponse::Cookie(CookieResponse(cookie))
+                let cookie = try_opt!(cookies.pop(),
+                                      ErrorStatus::NoSuchCookie,
+                                      format!("No cookie with name {}", name));
+                WebDriverResponse::Cookie(CookieResponse { value: cookie })
             }
             FindElement(_) | FindElementElement(_, _) => {
-                let element = try!(self.to_web_element(try_opt!(
-                    resp.result.get("value"),
-                    ErrorStatus::UnknownError,
-                    "Failed to find value field"
-                )));
-                WebDriverResponse::Generic(ValueResponse(serde_json::to_value(element)?))
-            }
+                let element = try!(self.to_web_element(
+                    try_opt!(resp.result.find("value"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find value field")));
+                WebDriverResponse::Generic(ValueResponse::new(element.to_json()))
+            },
             FindElements(_) | FindElementElements(_, _) => {
-                let element_vec = try_opt!(
-                    resp.result.as_array(),
-                    ErrorStatus::UnknownError,
-                    "Failed to interpret value as array"
-                );
-                let elements = try!(
-                    element_vec
-                        .iter()
-                        .map(|x| self.to_web_element(x))
-                        .collect::<Result<Vec<_>, _>>()
-                );
-                // TODO(Henrik): How to remove unwrap?
-                WebDriverResponse::Generic(ValueResponse(Value::Array(
-                    elements
-                        .iter()
-                        .map(|x| serde_json::to_value(x).unwrap())
-                        .collect(),
-                )))
-            }
+                let element_vec = try_opt!(resp.result.as_array(),
+                                           ErrorStatus::UnknownError,
+                                           "Failed to interpret value as array");
+                let elements = try!(element_vec.iter().map(
+                    |x| {
+                        self.to_web_element(x)
+                    }).collect::<Result<Vec<_>, _>>());
+                WebDriverResponse::Generic(ValueResponse::new(
+                    Json::Array(elements.iter().map(|x| {x.to_json()}).collect())))
+            },
             GetActiveElement => {
-                let element = try!(self.to_web_element(try_opt!(
-                    resp.result.get("value"),
-                    ErrorStatus::UnknownError,
-                    "Failed to find value field"
-                )));
-                WebDriverResponse::Generic(ValueResponse(serde_json::to_value(element)?))
-            }
+                let element = try!(self.to_web_element(
+                    try_opt!(resp.result.find("value"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find value field")));
+                WebDriverResponse::Generic(ValueResponse::new(element.to_json()))
+            },
             NewSession(_) => {
                 let session_id = try_opt!(
-                    try_opt!(
-                        resp.result.get("sessionId"),
-                        ErrorStatus::InvalidSessionId,
-                        "Failed to find sessionId field"
-                    ).as_str(),
+                    try_opt!(resp.result.find("sessionId"),
+                             ErrorStatus::InvalidSessionId,
+                             "Failed to find sessionId field").as_string(),
                     ErrorStatus::InvalidSessionId,
-                    "sessionId is not a string"
-                );
+                    "sessionId is not a string");
 
                 let mut capabilities = try_opt!(
-                    try_opt!(
-                        resp.result.get("capabilities"),
-                        ErrorStatus::UnknownError,
-                        "Failed to find capabilities field"
-                    ).as_object(),
+                    try_opt!(resp.result.find("capabilities"),
+                             ErrorStatus::UnknownError,
+                             "Failed to find capabilities field").as_object(),
                     ErrorStatus::UnknownError,
-                    "capabilities field is not an object"
-                ).clone();
+                    "capabilities field is not an object").clone();
 
                 capabilities.insert("moz:geckodriverVersion".into(), BuildInfo.into());
 
                 WebDriverResponse::NewSession(NewSessionResponse::new(
-                    session_id.to_string(),
-                    Value::Object(capabilities.clone()),
-                ))
-            }
-            DeleteSession => WebDriverResponse::DeleteSession,
-            Extension(ref extension) => match extension {
-                &GeckoExtensionCommand::GetContext => {
-                    WebDriverResponse::Generic(resp.to_value_response(true)?)
+                    session_id.to_string(), Json::Object(capabilities)))
+            },
+            DeleteSession => {
+                WebDriverResponse::DeleteSession
+            },
+            Extension(ref extension) => {
+                match extension {
+                    &GeckoExtensionCommand::GetContext => {
+                        let value = try_opt!(resp.result.find("value"),
+                                             ErrorStatus::UnknownError,
+                                             "Failed to find value field");
+                        WebDriverResponse::Generic(ValueResponse::new(value.clone()))
+                    },
+                    &GeckoExtensionCommand::SetContext(_) => WebDriverResponse::Void,
+                    &GeckoExtensionCommand::XblAnonymousChildren(_) => {
+                        let els_vec = try_opt!(resp.result.as_array(),
+                            ErrorStatus::UnknownError, "Failed to interpret body as array");
+                        let els = try!(els_vec.iter().map(|x| self.to_web_element(x))
+                            .collect::<Result<Vec<_>, _>>());
+                        WebDriverResponse::Generic(ValueResponse::new(
+                            Json::Array(els.iter().map(|el| el.to_json()).collect())))
+                    },
+                    &GeckoExtensionCommand::XblAnonymousByAttribute(_, _) => {
+                        let el = try!(self.to_web_element(try_opt!(resp.result.find("value"),
+                            ErrorStatus::UnknownError, "Failed to find value field")));
+                        WebDriverResponse::Generic(ValueResponse::new(el.to_json()))
+                    },
+                    &GeckoExtensionCommand::InstallAddon(_) => {
+                        let value = try_opt!(resp.result.find("value"),
+                                             ErrorStatus::UnknownError,
+                                             "Failed to find value field");
+                        WebDriverResponse::Generic(ValueResponse::new(value.clone()))
+                    },
+                    &GeckoExtensionCommand::UninstallAddon(_) => WebDriverResponse::Void
                 }
-                &GeckoExtensionCommand::SetContext(_) => WebDriverResponse::Void,
-                &GeckoExtensionCommand::XblAnonymousChildren(_) => {
-                    let els_vec = try_opt!(
-                        resp.result.as_array(),
-                        ErrorStatus::UnknownError,
-                        "Failed to interpret body as array"
-                    );
-                    let els = try!(
-                        els_vec
-                            .iter()
-                            .map(|x| self.to_web_element(x))
-                            .collect::<Result<Vec<_>, _>>()
-                    );
-                    WebDriverResponse::Generic(ValueResponse(serde_json::to_value(els)?))
-                }
-                &GeckoExtensionCommand::XblAnonymousByAttribute(_, _) => {
-                    let el = try!(self.to_web_element(try_opt!(
-                        resp.result.get("value"),
-                        ErrorStatus::UnknownError,
-                        "Failed to find value field"
-                    )));
-                    WebDriverResponse::Generic(ValueResponse(serde_json::to_value(el)?))
-                }
-                &GeckoExtensionCommand::InstallAddon(_) => {
-                    WebDriverResponse::Generic(resp.to_value_response(true)?)
-                }
-                &GeckoExtensionCommand::UninstallAddon(_) => WebDriverResponse::Void,
-            },
+            }
         })
     }
+
+    fn process_cookies(&self, json_data: &Json) -> WebDriverResult<Vec<Cookie>> {
+        let value = try_opt!(json_data.as_array(),
+                             ErrorStatus::UnknownError,
+                             "Failed to interpret value as array");
+        value.iter().map(|x| {
+            let name = try_opt!(
+                try_opt!(x.find("name"),
+                         ErrorStatus::UnknownError,
+                         "Cookie must have a name field").as_string(),
+                ErrorStatus::UnknownError,
+                "Cookie must have string name").to_string();
+            let value = try_opt!(
+                try_opt!(x.find("value"),
+                         ErrorStatus::UnknownError,
+                         "Cookie must have a value field").as_string(),
+                ErrorStatus::UnknownError,
+                "Cookie must have a string value").to_string();
+            let path = try!(
+                Nullable::from_json(x.find("path").unwrap_or(&Json::Null),
+                                    |x| {
+                                        Ok((try_opt!(x.as_string(),
+                                                     ErrorStatus::UnknownError,
+                                                     "Cookie path must be string")).to_string())
+                                    }));
+            let domain = try!(
+                Nullable::from_json(x.find("domain").unwrap_or(&Json::Null),
+                                    |x| {
+                                        Ok((try_opt!(x.as_string(),
+                                                     ErrorStatus::UnknownError,
+                                                     "Cookie domain must be string")).to_string())
+                                    }));
+            let expiry = try!(
+                Nullable::from_json(x.find("expiry").unwrap_or(&Json::Null),
+                                    |x| {
+                                        Ok(Date::new(try_opt!(
+                                            x.as_u64(),
+                                            ErrorStatus::UnknownError,
+                                            "Cookie expiry must be a positive integer")))
+                                    }));
+            let secure = try_opt!(
+                x.find("secure").map_or(Some(false), |x| x.as_boolean()),
+                ErrorStatus::UnknownError,
+                "Cookie secure flag must be boolean");
+            let http_only = try_opt!(
+                x.find("httpOnly").map_or(Some(false), |x| x.as_boolean()),
+                ErrorStatus::UnknownError,
+                "Cookie httpOnly flag must be boolean");
+
+            let new_cookie = Cookie {
+                name: name,
+                value: value,
+                path: path,
+                domain: domain,
+                expiry: expiry,
+                secure: secure,
+                httpOnly: http_only,
+            };
+            Ok(new_cookie)
+        }).collect::<Result<Vec<_>, _>>()
+    }
 }
 
-#[derive(Debug, PartialEq)]
 pub struct MarionetteCommand {
     pub id: u64,
     pub name: String,
-    pub params: Map<String, Value>,
-}
-
-impl Serialize for MarionetteCommand {
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: Serializer,
-    {
-        let data = (&0, &self.id, &self.name, &self.params);
-        data.serialize(serializer)
-    }
+    pub params: BTreeMap<String, Json>
 }
 
 impl MarionetteCommand {
-    fn new(id: u64, name: String, params: Map<String, Value>) -> MarionetteCommand {
+    fn new(id: u64, name: String, params: BTreeMap<String, Json>) -> MarionetteCommand {
         MarionetteCommand {
             id: id,
             name: name,
             params: params,
         }
     }
 
-    fn from_webdriver_message(
-        id: u64,
-        capabilities: Option<Map<String, Value>>,
-        msg: &WebDriverMessage<GeckoExtensionRoute>,
-    ) -> WebDriverResult<MarionetteCommand> {
+    fn from_webdriver_message(id: u64,
+                              capabilities: Option<BTreeMap<String, Json>>,
+                              msg: &WebDriverMessage<GeckoExtensionRoute>)
+                              -> WebDriverResult<MarionetteCommand> {
         let (opt_name, opt_parameters) = match msg.command {
             Status => panic!("Got status command that should already have been handled"),
             AcceptAlert => {
                 // Needs to be updated to "WebDriver:AcceptAlert" for Firefox 63
                 (Some("WebDriver:AcceptDialog"), None)
             }
             AddCookie(ref x) => (Some("WebDriver:AddCookie"), Some(x.to_marionette())),
             CloseWindow => (Some("WebDriver:CloseWindow"), None),
             DeleteCookie(ref x) => {
-                let mut data = Map::new();
-                data.insert("name".to_string(), Value::String(x.clone()));
+                let mut data = BTreeMap::new();
+                data.insert("name".to_string(), x.to_json());
                 (Some("WebDriver:DeleteCookie"), Some(Ok(data)))
             }
             DeleteCookies => (Some("WebDriver:DeleteAllCookies"), None),
             DeleteSession => {
-                let mut body = Map::new();
-                body.insert(
-                    "flags".to_owned(),
-                    serde_json::to_value(vec!["eForceQuit".to_string()])?,
-                );
+                let mut body = BTreeMap::new();
+                body.insert("flags".to_owned(), vec!["eForceQuit".to_json()].to_json());
                 (Some("Marionette:Quit"), Some(Ok(body)))
             }
             DismissAlert => (Some("WebDriver:DismissAlert"), None),
             ElementClear(ref x) => (Some("WebDriver:ElementClear"), Some(x.to_marionette())),
             ElementClick(ref x) => (Some("WebDriver:ElementClick"), Some(x.to_marionette())),
             ElementSendKeys(ref e, ref x) => {
-                let mut data = Map::new();
-                data.insert("id".to_string(), Value::String(e.id.clone()));
-                data.insert("text".to_string(), Value::String(x.text.clone()));
+                let mut data = BTreeMap::new();
+                data.insert("id".to_string(), e.id.to_json());
+                data.insert("text".to_string(), x.text.to_json());
                 data.insert(
                     "value".to_string(),
-                    serde_json::to_value(
-                        x.text
-                            .chars()
-                            .map(|x| x.to_string())
-                            .collect::<Vec<String>>(),
-                    )?,
+                    x.text
+                        .chars()
+                        .map(|x| x.to_string())
+                        .collect::<Vec<String>>()
+                        .to_json(),
                 );
                 (Some("WebDriver:ElementSendKeys"), Some(Ok(data)))
             }
             ElementTap(ref x) => (Some("singleTap"), Some(x.to_marionette())),
             ExecuteAsyncScript(ref x) => (
                 Some("WebDriver:ExecuteAsyncScript"),
                 Some(x.to_marionette()),
             ),
             ExecuteScript(ref x) => (Some("WebDriver:ExecuteScript"), Some(x.to_marionette())),
             FindElement(ref x) => (Some("WebDriver:FindElement"), Some(x.to_marionette())),
             FindElementElement(ref e, ref x) => {
                 let mut data = try!(x.to_marionette());
-                data.insert("element".to_string(), Value::String(e.id.clone()));
+                data.insert("element".to_string(), e.id.to_json());
                 (Some("WebDriver:FindElement"), Some(Ok(data)))
             }
             FindElements(ref x) => (Some("WebDriver:FindElements"), Some(x.to_marionette())),
             FindElementElements(ref e, ref x) => {
                 let mut data = try!(x.to_marionette());
-                data.insert("element".to_string(), Value::String(e.id.clone()));
+                data.insert("element".to_string(), e.id.to_json());
                 (Some("WebDriver:FindElements"), Some(Ok(data)))
             }
             FullscreenWindow => (Some("WebDriver:FullscreenWindow"), None),
             Get(ref x) => (Some("WebDriver:Navigate"), Some(x.to_marionette())),
             GetAlertText => (Some("WebDriver:GetAlertText"), None),
             GetActiveElement => (Some("WebDriver:GetActiveElement"), None),
             GetCookies | GetNamedCookie(_) => (Some("WebDriver:GetCookies"), None),
             GetCurrentUrl => (Some("WebDriver:GetCurrentURL"), None),
             GetCSSValue(ref e, ref x) => {
-                let mut data = Map::new();
-                data.insert("id".to_string(), Value::String(e.id.clone()));
-                data.insert("propertyName".to_string(), Value::String(x.clone()));
+                let mut data = BTreeMap::new();
+                data.insert("id".to_string(), e.id.to_json());
+                data.insert("propertyName".to_string(), x.to_json());
                 (Some("WebDriver:GetElementCSSValue"), Some(Ok(data)))
             }
             GetElementAttribute(ref e, ref x) => {
-                let mut data = Map::new();
-                data.insert("id".to_string(), Value::String(e.id.clone()));
-                data.insert("name".to_string(), Value::String(x.clone()));
+                let mut data = BTreeMap::new();
+                data.insert("id".to_string(), e.id.to_json());
+                data.insert("name".to_string(), x.to_json());
                 (Some("WebDriver:GetElementAttribute"), Some(Ok(data)))
             }
             GetElementProperty(ref e, ref x) => {
-                let mut data = Map::new();
-                data.insert("id".to_string(), Value::String(e.id.clone()));
-                data.insert("name".to_string(), Value::String(x.clone()));
+                let mut data = BTreeMap::new();
+                data.insert("id".to_string(), e.id.to_json());
+                data.insert("name".to_string(), x.to_json());
                 (Some("WebDriver:GetElementProperty"), Some(Ok(data)))
             }
             GetElementRect(ref x) => (Some("WebDriver:GetElementRect"), Some(x.to_marionette())),
             GetElementTagName(ref x) => {
                 (Some("WebDriver:GetElementTagName"), Some(x.to_marionette()))
             }
             GetElementText(ref x) => (Some("WebDriver:GetElementText"), Some(x.to_marionette())),
             GetPageSource => (Some("WebDriver:GetPageSource"), None),
@@ -1146,154 +1128,199 @@ impl MarionetteCommand {
             IsEnabled(ref x) => (Some("WebDriver:IsElementEnabled"), Some(x.to_marionette())),
             IsSelected(ref x) => (Some("WebDriver:IsElementSelected"), Some(x.to_marionette())),
             MaximizeWindow => (Some("WebDriver:MaximizeWindow"), None),
             MinimizeWindow => (Some("WebDriver:MinimizeWindow"), None),
             NewSession(_) => {
                 let caps = capabilities
                     .expect("Tried to create new session without processing capabilities");
 
-                let mut data = Map::new();
+                let mut data = BTreeMap::new();
                 for (k, v) in caps.iter() {
-                    data.insert(k.to_string(), serde_json::to_value(v)?);
+                    data.insert(k.to_string(), v.to_json());
                 }
 
                 (Some("WebDriver:NewSession"), Some(Ok(data)))
             }
             PerformActions(ref x) => (Some("WebDriver:PerformActions"), Some(x.to_marionette())),
             Refresh => (Some("WebDriver:Refresh"), None),
             ReleaseActions => (Some("WebDriver:ReleaseActions"), None),
             SendAlertText(ref x) => {
-                let mut data = Map::new();
-                data.insert("text".to_string(), Value::String(x.text.clone()));
+                let mut data = BTreeMap::new();
+                data.insert("text".to_string(), x.text.to_json());
                 data.insert(
                     "value".to_string(),
-                    serde_json::to_value(
-                        x.text
-                            .chars()
-                            .map(|x| x.to_string())
-                            .collect::<Vec<String>>(),
-                    )?,
+                    x.text
+                        .chars()
+                        .map(|x| x.to_string())
+                        .collect::<Vec<String>>()
+                        .to_json(),
                 );
                 (Some("WebDriver:SendAlertText"), Some(Ok(data)))
             }
             SetTimeouts(ref x) => (Some("WebDriver:SetTimeouts"), Some(x.to_marionette())),
             SetWindowRect(ref x) => (Some("WebDriver:SetWindowRect"), Some(x.to_marionette())),
             SwitchToFrame(ref x) => (Some("WebDriver:SwitchToFrame"), Some(x.to_marionette())),
             SwitchToParentFrame => (Some("WebDriver:SwitchToParentFrame"), None),
             SwitchToWindow(ref x) => (Some("WebDriver:SwitchToWindow"), Some(x.to_marionette())),
             TakeElementScreenshot(ref e) => {
-                let mut data = Map::new();
-                data.insert("element".to_string(), serde_json::to_value(e)?);
-                // data.insert("id".to_string(), e.id.to_json());
-                data.insert("highlights".to_string(), Value::Array(vec![]));
-                data.insert("full".to_string(), Value::Bool(false));
+                let mut data = BTreeMap::new();
+                data.insert("id".to_string(), e.id.to_json());
+                data.insert("highlights".to_string(), Json::Array(vec![]));
+                data.insert("full".to_string(), Json::Boolean(false));
                 (Some("WebDriver:TakeScreenshot"), Some(Ok(data)))
             }
             TakeScreenshot => {
-                let mut data = Map::new();
-                data.insert("id".to_string(), Value::Null);
-                data.insert("highlights".to_string(), Value::Array(vec![]));
-                data.insert("full".to_string(), Value::Bool(false));
+                let mut data = BTreeMap::new();
+                data.insert("id".to_string(), Json::Null);
+                data.insert("highlights".to_string(), Json::Array(vec![]));
+                data.insert("full".to_string(), Json::Boolean(false));
                 (Some("WebDriver:TakeScreenshot"), Some(Ok(data)))
             }
             Extension(ref extension) => match extension {
                 &GeckoExtensionCommand::GetContext => (Some("Marionette:GetContext"), None),
                 &GeckoExtensionCommand::InstallAddon(ref x) => {
                     (Some("Addon:Install"), Some(x.to_marionette()))
                 }
                 &GeckoExtensionCommand::SetContext(ref x) => {
                     (Some("Marionette:SetContext"), Some(x.to_marionette()))
                 }
                 &GeckoExtensionCommand::UninstallAddon(ref x) => {
                     (Some("Addon:Uninstall"), Some(x.to_marionette()))
                 }
                 &GeckoExtensionCommand::XblAnonymousByAttribute(ref e, ref x) => {
                     let mut data = try!(x.to_marionette());
-                    data.insert("element".to_string(), Value::String(e.id.clone()));
+                    data.insert("element".to_string(), e.id.to_json());
                     (Some("WebDriver:FindElement"), Some(Ok(data)))
                 }
                 &GeckoExtensionCommand::XblAnonymousChildren(ref e) => {
-                    let mut data = Map::new();
-                    data.insert("using".to_owned(), serde_json::to_value("anon")?);
-                    data.insert("value".to_owned(), Value::Null);
-                    data.insert("element".to_string(), serde_json::to_value(e.id.clone())?);
+                    let mut data = BTreeMap::new();
+                    data.insert("using".to_owned(), "anon".to_json());
+                    data.insert("value".to_owned(), Json::Null);
+                    data.insert("element".to_string(), e.id.to_json());
                     (Some("WebDriver:FindElements"), Some(Ok(data)))
                 }
             },
         };
 
-        let name = try_opt!(
-            opt_name,
-            ErrorStatus::UnsupportedOperation,
-            "Operation not supported"
-        );
-        let parameters = try!(opt_parameters.unwrap_or(Ok(Map::new())));
+        let name = try_opt!(opt_name,
+                            ErrorStatus::UnsupportedOperation,
+                            "Operation not supported");
+        let parameters = try!(opt_parameters.unwrap_or(Ok(BTreeMap::new())));
 
         Ok(MarionetteCommand::new(id, name.into(), parameters))
     }
 }
 
-#[derive(Debug, PartialEq)]
+impl ToJson for MarionetteCommand {
+    fn to_json(&self) -> Json {
+        Json::Array(vec![Json::U64(0), self.id.to_json(), self.name.to_json(),
+                         self.params.to_json()])
+    }
+}
+
 pub struct MarionetteResponse {
     pub id: u64,
     pub error: Option<MarionetteError>,
-    pub result: Value,
+    pub result: Json,
 }
 
-impl<'de> Deserialize<'de> for MarionetteResponse {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        #[derive(Deserialize)]
-        struct ResponseWrapper {
-            msg_type: u64,
-            id: u64,
-            error: Option<MarionetteError>,
-            result: Value,
+impl MarionetteResponse {
+    fn from_json(data: &Json) -> WebDriverResult<MarionetteResponse> {
+        let data_array = try_opt!(data.as_array(),
+                                  ErrorStatus::UnknownError,
+                                  "Expected a json array");
+
+        if data_array.len() != 4 {
+            return Err(WebDriverError::new(
+                ErrorStatus::UnknownError,
+                "Expected an array of length 4"));
         }
 
-        let wrapper: ResponseWrapper = Deserialize::deserialize(deserializer)?;
-
-        if wrapper.msg_type != 1 {
-            return Err(de::Error::custom(
-                "Expected '1' in first element of response",
-            ));
+        if data_array[0].as_u64() != Some(1) {
+            return Err(WebDriverError::new(ErrorStatus::UnknownError,
+                                           "Expected 1 in first element of response"));
+        };
+        let id = try_opt!(data[1].as_u64(),
+                          ErrorStatus::UnknownError,
+                          "Expected an integer id");
+        let error = if data[2].is_object() {
+            Some(try!(MarionetteError::from_json(&data[2])))
+        } else if data[2].is_null() {
+            None
+        } else {
+            return Err(WebDriverError::new(ErrorStatus::UnknownError,
+                                           "Expected object or null error"));
         };
 
-        Ok(MarionetteResponse {
-            id: wrapper.id,
-            error: wrapper.error,
-            result: wrapper.result,
-        })
+        let result = if data[3].is_null() || data[3].is_object() || data[3].is_array() {
+            data[3].clone()
+        } else {
+            return Err(WebDriverError::new(ErrorStatus::UnknownError,
+                                           "Expected object params"));
+        };
+
+        Ok(MarionetteResponse {id: id,
+                               error: error,
+                               result: result})
     }
 }
 
-impl MarionetteResponse {
-    fn to_value_response(self, value_required: bool) -> WebDriverResult<ValueResponse> {
-        let value: &Value = match value_required {
-            true => try_opt!(
-                self.result.get("value"),
-                ErrorStatus::UnknownError,
-                "Failed to find value field"
-            ),
-            false => &self.result,
-        };
-
-        Ok(ValueResponse(value.clone()))
+impl ToJson for MarionetteResponse {
+    fn to_json(&self) -> Json {
+        Json::Array(vec![Json::U64(1), self.id.to_json(), self.error.to_json(),
+                         self.result.clone()])
     }
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[derive(RustcEncodable, RustcDecodable)]
 pub struct MarionetteError {
-    #[serde(rename = "error")]
     pub code: String,
     pub message: String,
-    pub stacktrace: Option<String>,
+    pub stacktrace: Option<String>
+}
+
+impl MarionetteError {
+    fn from_json(data: &Json) -> WebDriverResult<MarionetteError> {
+        if !data.is_object() {
+            return Err(WebDriverError::new(ErrorStatus::UnknownError,
+                                           "Expected an error object"));
+        }
+
+        let code = try_opt!(
+            try_opt!(data.find("error"),
+                     ErrorStatus::UnknownError,
+                     "Error value has no error code").as_string(),
+            ErrorStatus::UnknownError,
+            "Error status was not a string").into();
+        let message = try_opt!(
+            try_opt!(data.find("message"),
+                     ErrorStatus::UnknownError,
+                     "Error value has no message").as_string(),
+            ErrorStatus::UnknownError,
+            "Error message was not a string").into();
+        let stacktrace = match data.find("stacktrace") {
+            None | Some(&Json::Null) => None,
+            Some(x) => Some(try_opt!(x.as_string(),
+                                     ErrorStatus::UnknownError,
+                                     "Error message was not a string").into()),
+        };
+
+        Ok(MarionetteError { code, message, stacktrace })
+    }
+}
+
+impl ToJson for MarionetteError {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("error".into(), self.code.to_json());
+        data.insert("message".into(), self.message.to_json());
+        data.insert("stacktrace".into(), self.stacktrace.to_json());
+        Json::Object(data)
+    }
 }
 
 impl Into<WebDriverError> for MarionetteError {
     fn into(self) -> WebDriverError {
         let status = ErrorStatus::from(self.code);
         let message = self.message;
 
         if let Some(stack) = self.stacktrace {
@@ -1308,17 +1335,17 @@ fn get_free_port() -> IoResult<u16> {
     TcpListener::bind((DEFAULT_HOST, 0))
         .and_then(|stream| stream.local_addr())
         .map(|x| x.port())
 }
 
 pub struct MarionetteConnection {
     port: u16,
     stream: Option<TcpStream>,
-    pub session: MarionetteSession,
+    pub session: MarionetteSession
 }
 
 impl MarionetteConnection {
     pub fn new(port: u16, session_id: Option<String>) -> MarionetteConnection {
         MarionetteConnection {
             port: port,
             stream: None,
             session: MarionetteSession::new(session_id),
@@ -1370,93 +1397,91 @@ impl MarionetteConnection {
                             ErrorStatus::UnknownError,
                             e.description().to_owned(),
                         ));
                     }
                 }
             }
         }
 
-        let data = self.handshake()?;
-        self.session.protocol = Some(data.protocol);
-        self.session.application_type = Some(data.application_type);
+        debug!("Connected to Marionette on {}:{}", DEFAULT_HOST, self.port);
+        self.handshake()
+    }
+
+    fn handshake(&mut self) -> WebDriverResult<()> {
+        let resp = try!(self.read_resp());
+        let handshake_data = try!(Json::from_str(&*resp));
+
+        let data = try_opt!(handshake_data.as_object(),
+                            ErrorStatus::UnknownError,
+                            "Expected a json object in handshake");
 
-        debug!("Connected to Marionette on {}:{}", DEFAULT_HOST, self.port);
+        self.session.protocol = Some(try_opt!(data.get("marionetteProtocol"),
+                                              ErrorStatus::UnknownError,
+                                              "Missing 'marionetteProtocol' field in handshake").to_string());
+
+        self.session.application_type = Some(try_opt!(data.get("applicationType"),
+                                              ErrorStatus::UnknownError,
+                                              "Missing 'applicationType' field in handshake").to_string());
+
+        if self.session.protocol != Some("3".into()) {
+            return Err(WebDriverError::new(
+                ErrorStatus::UnknownError,
+                format!("Unsupported Marionette protocol version {}, required 3",
+                        self.session.protocol.as_ref().unwrap_or(&"<unknown>".into()))));
+        }
+
         Ok(())
     }
 
-    fn handshake(&mut self) -> WebDriverResult<MarionetteHandshake> {
-        let resp = self.read_resp()?;
-        let data = serde_json::from_str::<MarionetteHandshake>(&resp)?;
+    pub fn close(&self) {
+    }
 
-        if data.protocol != 3 {
-            return Err(WebDriverError::new(
-                ErrorStatus::UnknownError,
-                format!(
-                    "Unsupported Marionette protocol version {}, required 3",
-                    data.protocol
-                ),
-            ));
-        }
-
-        Ok(data)
+    fn encode_msg(&self, msg:Json) -> String {
+        let data = json::encode(&msg).unwrap();
+        format!("{}:{}", data.len(), data)
     }
 
-    pub fn close(&self) {}
+    pub fn send_command(&mut self,
+                        capabilities: Option<BTreeMap<String, Json>>,
+                        msg: &WebDriverMessage<GeckoExtensionRoute>)
+                        -> WebDriverResult<WebDriverResponse> {
+        let id = self.session.next_command_id();
+        let command = try!(MarionetteCommand::from_webdriver_message(id, capabilities, msg));
 
-    fn encode_msg(&self, msg: MarionetteCommand) -> WebDriverResult<String> {
-        let data = serde_json::to_string(&msg)?;
+        let resp_data = try!(self.send(command.to_json()));
+        let json_data: Json = try!(Json::from_str(&*resp_data));
 
-        Ok(format!("{}:{}", data.len(), data))
+        self.session.response(msg, try!(MarionetteResponse::from_json(&json_data)))
     }
 
-    pub fn send_command(
-        &mut self,
-        capabilities: Option<Map<String, Value>>,
-        msg: &WebDriverMessage<GeckoExtensionRoute>,
-    ) -> WebDriverResult<WebDriverResponse> {
-        let id = self.session.next_command_id();
-        let command = MarionetteCommand::from_webdriver_message(id, capabilities, msg)?;
-        let resp_data = self.send(command)?;
-        let data: MarionetteResponse = serde_json::from_str(&resp_data)?;
-
-        self.session.response(msg, data)
-    }
-
-    fn send(&mut self, msg: MarionetteCommand) -> WebDriverResult<String> {
-        let data = self.encode_msg(msg)?;
+    fn send(&mut self, msg: Json) -> WebDriverResult<String> {
+        let data = self.encode_msg(msg);
 
         match self.stream {
             Some(ref mut stream) => {
                 if stream.write(&*data.as_bytes()).is_err() {
-                    let mut err = WebDriverError::new(
-                        ErrorStatus::UnknownError,
-                        "Failed to write response to stream",
-                    );
+                    let mut err = WebDriverError::new(ErrorStatus::UnknownError,
+                                                      "Failed to write response to stream");
                     err.delete_session = true;
                     return Err(err);
                 }
             }
             None => {
-                let mut err = WebDriverError::new(
-                    ErrorStatus::UnknownError,
-                    "Tried to write before opening stream",
-                );
+                let mut err = WebDriverError::new(ErrorStatus::UnknownError,
+                                                  "Tried to write before opening stream");
                 err.delete_session = true;
                 return Err(err);
             }
         }
-
         match self.read_resp() {
             Ok(resp) => Ok(resp),
             Err(_) => {
-                let mut err = WebDriverError::new(
-                    ErrorStatus::UnknownError,
-                    "Failed to decode response from marionette",
-                );
+                let mut err = WebDriverError::new(ErrorStatus::UnknownError,
+                                                  "Failed to decode response from marionette");
                 err.delete_session = true;
                 Err(err)
             }
         }
     }
 
     fn read_resp(&mut self) -> IoResult<String> {
         let mut bytes = 0usize;
@@ -1487,410 +1512,213 @@ impl MarionetteConnection {
         }
 
         let buf = &mut [0 as u8; 8192];
         let mut payload = Vec::with_capacity(bytes);
         let mut total_read = 0;
         while total_read < bytes {
             let num_read = try!(stream.read(buf));
             if num_read == 0 {
-                return Err(IoError::new(
-                    ErrorKind::Other,
-                    "EOF reading marionette message",
-                ));
+                return Err(IoError::new(ErrorKind::Other,
+                                        "EOF reading marionette message"))
             }
             total_read += num_read;
             for x in &buf[..num_read] {
                 payload.push(*x);
             }
         }
 
         // TODO(jgraham): Need to handle the error here
         Ok(String::from_utf8(payload).unwrap())
     }
 }
 
 trait ToMarionette {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>>;
-}
-
-impl ToMarionette for ActionsParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        Ok(try_opt!(
-            serde_json::to_value(self)?.as_object(),
-            ErrorStatus::UnknownError,
-            "Expected an object"
-        ).clone())
-    }
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>>;
 }
 
-impl ToMarionette for AddCookieParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut cookie = Map::new();
-        cookie.insert("name".to_string(), serde_json::to_value(&self.name)?);
-        cookie.insert("value".to_string(), serde_json::to_value(&self.value)?);
-        if self.path.is_some() {
-            cookie.insert("path".to_string(), serde_json::to_value(&self.path)?);
-        }
-        if self.domain.is_some() {
-            cookie.insert("domain".to_string(), serde_json::to_value(&self.domain)?);
-        }
-        if self.expiry.is_some() {
-            cookie.insert("expiry".to_string(), serde_json::to_value(&self.expiry)?);
-        }
-        cookie.insert("secure".to_string(), serde_json::to_value(self.secure)?);
-        cookie.insert("httpOnly".to_string(), serde_json::to_value(self.httpOnly)?);
-
-        let mut data = Map::new();
-        data.insert("cookie".to_string(), serde_json::to_value(cookie)?);
-        Ok(data)
+impl ToMarionette for GetParameters {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        Ok(try_opt!(self.to_json().as_object(), ErrorStatus::UnknownError, "Expected an object").clone())
     }
 }
 
-impl ToMarionette for FrameId {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut data = Map::new();
-        match *self {
-            FrameId::Short(x) => data.insert("id".to_string(), serde_json::to_value(x)?),
-            FrameId::Element(ref x) => data.insert(
-                "element".to_string(),
-                Value::Object(try!(x.to_marionette())),
-            ),
-        };
-        Ok(data)
+impl ToMarionette for TimeoutsParameters {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        Ok(try_opt!(self.to_json().as_object(), ErrorStatus::UnknownError, "Expected an object").clone())
     }
 }
 
-impl ToMarionette for GetNamedCookieParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        Ok(try_opt!(
-            serde_json::to_value(self)?.as_object(),
-            ErrorStatus::UnknownError,
-            "Expected an object"
-        ).clone())
+impl ToMarionette for WindowRectParameters {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        Ok(try_opt!(self.to_json().as_object(), ErrorStatus::UnknownError, "Expected an object").clone())
     }
 }
 
-impl ToMarionette for GetParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        Ok(try_opt!(
-            serde_json::to_value(self)?.as_object(),
-            ErrorStatus::UnknownError,
-            "Expected an object"
-        ).clone())
-    }
-}
-
-impl ToMarionette for JavascriptCommandParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut data = serde_json::to_value(self)?.as_object().unwrap().clone();
-        data.insert("newSandbox".to_string(), Value::Bool(false));
-        data.insert("specialPowers".to_string(), Value::Bool(false));
-        data.insert("scriptTimeout".to_string(), Value::Null);
+impl ToMarionette for SwitchToWindowParameters {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        let mut data = BTreeMap::new();
+        data.insert("name".to_string(), self.handle.to_json());
         Ok(data)
     }
 }
 
 impl ToMarionette for LocatorParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        Ok(try_opt!(
-            serde_json::to_value(self)?.as_object(),
-            ErrorStatus::UnknownError,
-            "Expected an object"
-        ).clone())
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        Ok(try_opt!(self.to_json().as_object(),
+                    ErrorStatus::UnknownError,
+                    "Expected an object")
+            .clone())
     }
 }
 
 impl ToMarionette for SwitchToFrameParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut data = Map::new();
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        let mut data = BTreeMap::new();
         let key = match self.id {
-            None => None,
-            Some(FrameId::Short(_)) => Some("id"),
-            Some(FrameId::Element(_)) => Some("element"),
+            FrameId::Null => None,
+            FrameId::Short(_) => Some("id"),
+            FrameId::Element(_) => Some("element"),
         };
         if let Some(x) = key {
-            data.insert(x.to_string(), serde_json::to_value(&self.id)?);
+            data.insert(x.to_string(), self.id.to_json());
         }
         Ok(data)
     }
 }
 
-impl ToMarionette for SwitchToWindowParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut data = Map::new();
-        data.insert(
-            "name".to_string(),
-            serde_json::to_value(self.handle.clone())?,
-        );
+impl ToMarionette for JavascriptCommandParameters {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        let mut data = self.to_json().as_object().unwrap().clone();
+        data.insert("newSandbox".to_string(), false.to_json());
+        data.insert("specialPowers".to_string(), false.to_json());
+        data.insert("scriptTimeout".to_string(), Json::Null);
+        Ok(data)
+    }
+}
+
+impl ToMarionette for ActionsParameters {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        Ok(try_opt!(self.to_json().as_object(),
+                    ErrorStatus::UnknownError,
+                    "Expected an object")
+            .clone())
+    }
+}
+
+impl ToMarionette for GetNamedCookieParameters {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        Ok(try_opt!(self.to_json().as_object(),
+                    ErrorStatus::UnknownError,
+                    "Expected an object")
+            .clone())
+    }
+}
+
+impl ToMarionette for AddCookieParameters {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        let mut cookie = BTreeMap::new();
+        cookie.insert("name".to_string(), self.name.to_json());
+        cookie.insert("value".to_string(), self.value.to_json());
+        if self.path.is_value() {
+            cookie.insert("path".to_string(), self.path.to_json());
+        }
+        if self.domain.is_value() {
+            cookie.insert("domain".to_string(), self.domain.to_json());
+        }
+        if self.expiry.is_value() {
+            cookie.insert("expiry".to_string(), self.expiry.to_json());
+        }
+        cookie.insert("secure".to_string(), self.secure.to_json());
+        cookie.insert("httpOnly".to_string(), self.httpOnly.to_json());
+        let mut data = BTreeMap::new();
+        data.insert("cookie".to_string(), Json::Object(cookie));
         Ok(data)
     }
 }
 
 impl ToMarionette for TakeScreenshotParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut data = Map::new();
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        let mut data = BTreeMap::new();
         let element = match self.element {
-            None => Value::Null,
-            Some(ref x) => Value::Object(try!(x.to_marionette())),
+            Nullable::Null => Json::Null,
+            Nullable::Value(ref x) => Json::Object(try!(x.to_marionette()))
         };
         data.insert("element".to_string(), element);
         Ok(data)
     }
 }
 
-impl ToMarionette for TimeoutsParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        Ok(try_opt!(
-            serde_json::to_value(self)?.as_object(),
-            ErrorStatus::UnknownError,
-            "Expected an object"
-        ).clone())
-    }
-}
-
 impl ToMarionette for WebElement {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut data = Map::new();
-        data.insert("id".to_string(), serde_json::to_value(&self.id)?);
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        let mut data = BTreeMap::new();
+        data.insert("id".to_string(), self.id.to_json());
         Ok(data)
     }
 }
 
-impl ToMarionette for WindowRectParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        Ok(try_opt!(
-            serde_json::to_value(self)?.as_object(),
-            ErrorStatus::UnknownError,
-            "Expected an object"
-        ).clone())
+impl<T: ToJson> ToMarionette for Nullable<T> {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        //Note this is a terrible hack. We don't want Nullable<T: ToJson+ToMarionette>
+        //so in cases where ToJson != ToMarionette you have to deal with the Nullable
+        //explicitly. This kind of suggests that the whole design is wrong.
+        Ok(try_opt!(self.to_json().as_object(), ErrorStatus::UnknownError, "Expected an object").clone())
+    }
+}
+
+impl ToMarionette for FrameId {
+    fn to_marionette(&self) -> WebDriverResult<BTreeMap<String, Json>> {
+        let mut data = BTreeMap::new();
+        match *self {
+            FrameId::Short(x) => data.insert("id".to_string(), x.to_json()),
+            FrameId::Element(ref x) => data.insert("element".to_string(),
+                                                   Json::Object(try!(x.to_marionette()))),
+            FrameId::Null => None
+        };
+        Ok(data)
     }
 }
 
 #[cfg(test)]
 mod tests {
-    use super::*;
-    use std::fs::File;
+    use marionette::{AddonInstallParameters, Parameters};
+    use rustc_serialize::json::Json;
     use std::io::Read;
-    use test::check_deserialize;
-
-    #[test]
-    fn test_json_addon_install_parameters_null() {
-        let json = r#""#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_empty() {
-        let json = r#"{}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
+    use std::fs::File;
+    use webdriver::error::WebDriverResult;
 
     #[test]
-    fn test_json_addon_install_parameters_with_path() {
-        let json = r#"{"path": "/path/to.xpi", "temporary": true}"#;
-        let data = AddonInstallParameters {
-            path: "/path/to.xpi".to_string(),
-            temporary: true,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_path_invalid_type() {
-        let json = r#"{"path": true, "temporary": true}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_path_and_temporary_invalid_type() {
-        let json = r#"{"path": "/path/to.xpi", "temporary": "foo"}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_path_only() {
-        let json = r#"{"path": "/path/to.xpi"}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    fn test_addon_install_params_missing_path() {
+        let json_data: Json = Json::from_str(r#"{"temporary": true}"#).unwrap();
+        let res: WebDriverResult<AddonInstallParameters> = Parameters::from_json(&json_data);
+        assert!(res.is_err());
     }
 
     #[test]
-    fn test_json_addon_install_parameters_with_addon() {
-        let json = r#"{"addon": "aGVsbG8=", "temporary": true}"#;
-        let data = serde_json::from_str::<AddonInstallParameters>(&json).unwrap();
-
-        assert_eq!(data.temporary, true);
-        let mut file = File::open(data.path).unwrap();
-        let mut contents = String::new();
-        file.read_to_string(&mut contents).unwrap();
-        assert_eq!(contents, "hello");
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_addon_invalid_type() {
-        let json = r#"{"addon": true, "temporary": true}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_addon_and_temporary_invalid_type() {
-        let json = r#"{"addon": "aGVsbG8=", "temporary": "foo"}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_addon_only() {
-        let json = r#"{"addon": "aGVsbG8="}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_install_parameters_with_temporary_only() {
-        let json = r#"{"temporary": true}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_both_path_and_addon() {
-        let json = r#"{
-            "path":"/path/to.xpi",
-            "addon":"aGVsbG8=",
-            "temporary":true
-        }"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_uninstall_parameters_null() {
-        let json = r#""#;
-
-        assert!(serde_json::from_str::<AddonUninstallParameters>(&json).is_err());
+    fn test_addon_install_params_with_both_path_and_base64() {
+        let json_data: Json = Json::from_str(
+            r#"{"path": "/path/to.xpi", "addon": "aGVsbG8=", "temporary": true}"#).unwrap();
+        let res: WebDriverResult<AddonInstallParameters> = Parameters::from_json(&json_data);
+        assert!(res.is_err());
     }
 
     #[test]
-    fn test_json_addon_uninstall_parameters_empty() {
-        let json = r#"{}"#;
-
-        assert!(serde_json::from_str::<AddonUninstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_uninstall_parameters() {
-        let json = r#"{"id": "foo"}"#;
-        let data = AddonUninstallParameters {
-            id: "foo".to_string(),
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_addon_uninstall_parameters_id_invalid_type() {
-        let json = r#"{"id": true}"#;
-
-        assert!(serde_json::from_str::<AddonUninstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_gecko_context_parameters_content() {
-        let json = r#"{"context": "content"}"#;
-        let data = GeckoContextParameters {
-            context: GeckoContext::Content,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_gecko_context_parameters_chrome() {
-        let json = r#"{"context": "chrome"}"#;
-        let data = GeckoContextParameters {
-            context: GeckoContext::Chrome,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_gecko_context_parameters_context_missing() {
-        let json = r#"{}"#;
-
-        assert!(serde_json::from_str::<GeckoContextParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_gecko_context_parameters_context_null() {
-        let json = r#"{"context": null}"#;
-
-        assert!(serde_json::from_str::<GeckoContextParameters>(&json).is_err());
+    fn test_addon_install_params_with_path() {
+        let json_data: Json = Json::from_str(
+            r#"{"path": "/path/to.xpi", "temporary": true}"#).unwrap();
+        let parameters: AddonInstallParameters = Parameters::from_json(&json_data).unwrap();
+        assert_eq!(parameters.path, "/path/to.xpi");
+        assert_eq!(parameters.temporary, true);
     }
 
     #[test]
-    fn test_json_gecko_context_parameters_context_invalid_value() {
-        let json = r#"{"context": "foo"}"#;
-
-        assert!(serde_json::from_str::<GeckoContextParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute() {
-        let json = r#"{
-            "name": "foo",
-            "value": "bar"
-        }"#;
-
-        let data = XblLocatorParameters {
-            name: "foo".to_string(),
-            value: "bar".to_string(),
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute_with_name_missing() {
-        let json = r#"{
-            "value": "bar"
-        }"#;
+    fn test_addon_install_params_with_base64() {
+        let json_data: Json = Json::from_str(
+            r#"{"addon": "aGVsbG8=", "temporary": true}"#).unwrap();
+        let parameters: AddonInstallParameters = Parameters::from_json(&json_data).unwrap();
 
-        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute_with_name_invalid_type() {
-        let json = r#"{
-            "name": null,
-            "value": "bar"
-        }"#;
-
-        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute_with_value_missing() {
-        let json = r#"{
-            "name": "foo",
-        }"#;
-
-        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute_with_value_invalid_type() {
-        let json = r#"{
-            "name": "foo",
-            "value": null
-        }"#;
-
-        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
+        assert_eq!(parameters.temporary, true);
+        let mut file = File::open(parameters.path).unwrap();
+        let mut contents = String::new();
+        file.read_to_string(&mut contents).unwrap();
+        assert_eq!("hello", contents);
     }
 }
deleted file mode 100644
--- a/testing/geckodriver/src/test.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-use regex::Regex;
-use serde;
-use serde_json;
-use std;
-
-lazy_static! {
-    static ref MIN_REGEX: Regex = Regex::new(r"[\n\t]|\s{4}").unwrap();
-}
-
-pub fn check_deserialize<T>(json: &str, data: &T)
-where
-    T: std::fmt::Debug,
-    T: std::cmp::PartialEq,
-    T: serde::de::DeserializeOwned,
-{
-    let min_json = MIN_REGEX.replace_all(json, "");
-
-    assert_eq!(serde_json::from_str::<T>(&min_json).unwrap(), *data);
-}
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/webdriver/tests/new_session/merge.py.ini
@@ -0,0 +1,13 @@
+[merge.py]
+  [test_merge_platformName]
+    expected:
+      if os != "linux": FAIL
+
+  [test_platform_name[body0\]]
+    expected:
+      if os != "linux": FAIL
+
+  [test_platform_name[body1\]]
+    expected:
+      if os != "linux": FAIL
+
--- a/testing/webdriver/Cargo.toml
+++ b/testing/webdriver/Cargo.toml
@@ -6,19 +6,15 @@ description = "Library implementing the 
 keywords = ["webdriver", "browser", "automation", "protocol", "w3c"]
 documentation = "https://docs.rs/webdriver"
 repository = "https://hg.mozilla.org/mozilla-central/file/tip/testing/webdriver"
 readme = "README.md"
 license = "MPL-2.0"
 
 [dependencies]
 cookie = { version = "0.10", default-features = false }
-base64 = "0.6"
 hyper = "0.10"
-lazy_static = "1.0"
 log = "0.4"
 regex = "1.0"
-serde = "1.0"
-serde_json = "1.0"
-serde_derive = "1.0"
+rustc-serialize = "0.3"
 time = "0.1"
 unicode-segmentation = "1.2"
 url = "1"
--- a/testing/webdriver/src/actions.rs
+++ b/testing/webdriver/src/actions.rs
@@ -1,1048 +1,660 @@
-use common::WebElement;
-use serde::de::{self, Deserialize, Deserializer};
-use serde::ser::{Serialize, Serializer};
+use command::Parameters;
+use common::{Nullable, WebElement};
+use error::{WebDriverResult, WebDriverError, ErrorStatus};
+use rustc_serialize::json::{ToJson, Json};
+use unicode_segmentation::UnicodeSegmentation;
+use std::collections::BTreeMap;
 use std::default::Default;
-use unicode_segmentation::UnicodeSegmentation;
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq)]
 pub struct ActionSequence {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub id: Option<String>,
-    #[serde(flatten)]
-    pub actions: ActionsType,
+    pub id: Nullable<String>,
+    pub actions: ActionsType
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-#[serde(tag = "type")]
-pub enum ActionsType {
-    #[serde(rename = "none")]
-    Null { actions: Vec<NullActionItem> },
-    #[serde(rename = "key")]
-    Key { actions: Vec<KeyActionItem> },
-    #[serde(rename = "pointer")]
-    Pointer {
-        parameters: PointerActionParameters,
-        actions: Vec<PointerActionItem>,
-    },
-}
+impl Parameters for ActionSequence {
+    fn from_json(body: &Json) -> WebDriverResult<ActionSequence> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Actions chain was not an object");
+
+        let type_name = try_opt!(try_opt!(data.get("type"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing type parameter").as_string(),
+                                 ErrorStatus::InvalidArgument,
+                                 "Parameter ;type' was not a string");
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-#[serde(untagged)]
-pub enum NullActionItem {
-    General(GeneralAction),
-}
+        let id = match data.get("id") {
+            Some(x) => Some(try_opt!(x.as_string(),
+                                     ErrorStatus::InvalidArgument,
+                                     "Parameter 'id' was not a string").to_owned()),
+            None => None
+        };
+
+
+        // Note that unlike the spec we get the pointer parameters in ActionsType::from_json
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-#[serde(tag = "type")]
-pub enum GeneralAction {
-    #[serde(rename = "pause")]
-    Pause(PauseAction),
-}
+        let actions = match type_name {
+            "none" | "key" | "pointer" => try!(ActionsType::from_json(&body)),
+            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                "Invalid action type"))
+        };
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct PauseAction {
-    pub duration: u64,
+        Ok(ActionSequence {
+            id: id.into(),
+            actions: actions
+        })
+    }
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-#[serde(untagged)]
-pub enum KeyActionItem {
-    General(GeneralAction),
-    Key(KeyAction),
+impl ToJson for ActionSequence {
+    fn to_json(&self) -> Json {
+        let mut data: BTreeMap<String, Json> = BTreeMap::new();
+        data.insert("id".into(), self.id.to_json());
+        let (action_type, actions) = match self.actions {
+            ActionsType::Null(ref actions) => {
+                ("none",
+                 actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>())
+            }
+            ActionsType::Key(ref actions) => {
+                ("key",
+                 actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>())
+            }
+            ActionsType::Pointer(ref parameters, ref actions) => {
+                data.insert("parameters".into(), parameters.to_json());
+                ("pointer",
+                 actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>())
+            }
+        };
+        data.insert("type".into(), action_type.to_json());
+        data.insert("actions".into(), actions.to_json());
+        Json::Object(data)
+    }
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-#[serde(tag = "type")]
-pub enum KeyAction {
-    #[serde(rename = "keyDown")]
-    Down(KeyDownAction),
-    #[serde(rename = "keyUp")]
-    Up(KeyUpAction),
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct KeyDownAction {
-    #[serde(deserialize_with = "deserialize_key_action_value")]
-    pub value: String,
+#[derive(Debug, PartialEq)]
+pub enum ActionsType {
+    Null(Vec<NullActionItem>),
+    Key(Vec<KeyActionItem>),
+    Pointer(PointerActionParameters, Vec<PointerActionItem>)
 }
 
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct KeyUpAction {
-    #[serde(deserialize_with = "deserialize_key_action_value")]
-    pub value: String,
+impl Parameters for ActionsType {
+    fn from_json(body: &Json) -> WebDriverResult<ActionsType> {
+        // These unwraps are OK as long as this is only called from ActionSequence::from_json
+        let data = body.as_object().expect("Body should be a JSON Object");
+        let actions_type = body.find("type").and_then(|x| x.as_string()).expect("Type should be a string");
+        let actions_chain = try_opt!(try_opt!(data.get("actions"),
+                                              ErrorStatus::InvalidArgument,
+                                              "Missing actions parameter").as_array(),
+                                     ErrorStatus::InvalidArgument,
+                                     "Parameter 'actions' was not an array");
+        match actions_type {
+            "none" => {
+                let mut actions = Vec::with_capacity(actions_chain.len());
+                for action_body in actions_chain.iter() {
+                    actions.push(try!(NullActionItem::from_json(action_body)));
+                };
+                Ok(ActionsType::Null(actions))
+            },
+            "key" => {
+                let mut actions = Vec::with_capacity(actions_chain.len());
+                for action_body in actions_chain.iter() {
+                    actions.push(try!(KeyActionItem::from_json(action_body)));
+                };
+                Ok(ActionsType::Key(actions))
+            },
+            "pointer" => {
+                let mut actions = Vec::with_capacity(actions_chain.len());
+                let parameters = match data.get("parameters") {
+                    Some(x) => try!(PointerActionParameters::from_json(x)),
+                    None => Default::default()
+                };
+
+                for action_body in actions_chain.iter() {
+                    actions.push(try!(PointerActionItem::from_json(action_body)));
+                }
+                Ok(ActionsType::Pointer(parameters, actions))
+            }
+            _ => panic!("Got unexpected action type after checking type")
+        }
+    }
 }
 
-fn deserialize_key_action_value<'de, D>(deserializer: D) -> Result<String, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    String::deserialize(deserializer).map(|value| {
-        // Only a single Unicode grapheme cluster is allowed
-        if value.graphemes(true).collect::<Vec<&str>>().len() != 1 {
-            return Err(de::Error::custom(format!(
-                "'{}' should only contain a single Unicode code point",
-                value
-            )));
-        }
-
-        Ok(value)
-    })?
-}
-
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "lowercase")]
+#[derive(Debug, PartialEq)]
 pub enum PointerType {
     Mouse,
     Pen,
     Touch,
 }
 
+impl Parameters for PointerType {
+    fn from_json(body: &Json) -> WebDriverResult<PointerType> {
+        match body.as_string() {
+            Some("mouse") => Ok(PointerType::Mouse),
+            Some("pen") => Ok(PointerType::Pen),
+            Some("touch") => Ok(PointerType::Touch),
+            Some(_) => Err(WebDriverError::new(
+                ErrorStatus::InvalidArgument,
+                "Unsupported pointer type"
+            )),
+            None => Err(WebDriverError::new(
+                ErrorStatus::InvalidArgument,
+                "Pointer type was not a string"
+            ))
+        }
+    }
+}
+
+impl ToJson for PointerType {
+    fn to_json(&self) -> Json {
+        match *self {
+            PointerType::Mouse => "mouse".to_json(),
+            PointerType::Pen => "pen".to_json(),
+            PointerType::Touch => "touch".to_json(),
+        }.to_json()
+    }
+}
+
 impl Default for PointerType {
     fn default() -> PointerType {
         PointerType::Mouse
     }
 }
 
-#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Default, PartialEq)]
 pub struct PointerActionParameters {
-    #[serde(rename = "pointerType")]
-    pub pointer_type: PointerType,
+    pub pointer_type: PointerType
+}
+
+impl Parameters for PointerActionParameters {
+    fn from_json(body: &Json) -> WebDriverResult<PointerActionParameters> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Parameter 'parameters' was not an object");
+        let pointer_type = match data.get("pointerType") {
+            Some(x) => try!(PointerType::from_json(x)),
+            None => PointerType::default()
+        };
+        Ok(PointerActionParameters {
+            pointer_type: pointer_type
+        })
+    }
+}
+
+impl ToJson for PointerActionParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("pointerType".to_owned(),
+                    self.pointer_type.to_json());
+        Json::Object(data)
+    }
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-#[serde(untagged)]
-pub enum PointerActionItem {
+#[derive(Debug, PartialEq)]
+pub enum NullActionItem {
+    General(GeneralAction)
+}
+
+impl Parameters for NullActionItem {
+    fn from_json(body: &Json) -> WebDriverResult<NullActionItem> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Actions chain was not an object");
+        let type_name = try_opt!(
+            try_opt!(data.get("type"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'type' parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "Parameter 'type' was not a string");
+        match type_name {
+            "pause" => Ok(NullActionItem::General(
+                try!(GeneralAction::from_json(body)))),
+            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                "Invalid type attribute"))
+        }
+    }
+}
+
+impl ToJson for NullActionItem {
+    fn to_json(&self) -> Json {
+        match self {
+            &NullActionItem::General(ref x) => x.to_json(),
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum KeyActionItem {
     General(GeneralAction),
-    Pointer(PointerAction),
+    Key(KeyAction)
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-#[serde(tag = "type")]
-pub enum PointerAction {
-    #[serde(rename = "pointerCancel")]
-    Cancel,
-    #[serde(rename = "pointerDown")]
-    Down(PointerDownAction),
-    #[serde(rename = "pointerMove")]
-    Move(PointerMoveAction),
-    #[serde(rename = "pointerUp")]
-    Up(PointerUpAction),
+impl Parameters for KeyActionItem {
+    fn from_json(body: &Json) -> WebDriverResult<KeyActionItem> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Key action item was not an object");
+        let type_name = try_opt!(
+            try_opt!(data.get("type"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'type' parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "Parameter 'type' was not a string");
+        match type_name {
+            "pause" => Ok(KeyActionItem::General(
+                try!(GeneralAction::from_json(body)))),
+            _ => Ok(KeyActionItem::Key(
+                try!(KeyAction::from_json(body))))
+        }
+    }
+}
+
+impl ToJson for KeyActionItem {
+    fn to_json(&self) -> Json {
+        match *self {
+            KeyActionItem::General(ref x) => x.to_json(),
+            KeyActionItem::Key(ref x) => x.to_json()
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum PointerActionItem {
+    General(GeneralAction),
+    Pointer(PointerAction)
 }
 
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct PointerDownAction {
-    pub button: u64,
+impl Parameters for PointerActionItem {
+    fn from_json(body: &Json) -> WebDriverResult<PointerActionItem> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Pointer action item was not an object");
+        let type_name = try_opt!(
+            try_opt!(data.get("type"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'type' parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "Parameter 'type' was not a string");
+
+        match type_name {
+            "pause" => Ok(PointerActionItem::General(try!(GeneralAction::from_json(body)))),
+            _ => Ok(PointerActionItem::Pointer(try!(PointerAction::from_json(body))))
+        }
+    }
+}
+
+impl ToJson for PointerActionItem {
+    fn to_json(&self) -> Json {
+        match self {
+            &PointerActionItem::General(ref x) => x.to_json(),
+            &PointerActionItem::Pointer(ref x) => x.to_json()
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum GeneralAction {
+    Pause(PauseAction)
+}
+
+impl Parameters for GeneralAction {
+    fn from_json(body: &Json) -> WebDriverResult<GeneralAction> {
+        match body.find("type").and_then(|x| x.as_string()) {
+            Some("pause") => Ok(GeneralAction::Pause(try!(PauseAction::from_json(body)))),
+            _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                         "Invalid or missing type attribute"))
+        }
+    }
+}
+
+impl ToJson for GeneralAction {
+    fn to_json(&self) -> Json {
+        match self {
+            &GeneralAction::Pause(ref x) => x.to_json()
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct PauseAction {
+    pub duration: u64
 }
 
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct PointerMoveAction {
-    #[serde(
-        default,
-        skip_serializing_if = "Option::is_none",
-        deserialize_with = "deserialize_to_option_u64"
-    )]
-    pub duration: Option<u64>,
-    #[serde(default)]
-    pub origin: PointerOrigin,
-    #[serde(
-        default,
-        skip_serializing_if = "Option::is_none",
-        deserialize_with = "deserialize_to_option_i64"
-    )]
-    pub x: Option<i64>,
-    #[serde(
-        default,
-        skip_serializing_if = "Option::is_none",
-        deserialize_with = "deserialize_to_option_i64"
-    )]
-    pub y: Option<i64>,
+impl Parameters for PauseAction {
+    fn from_json(body: &Json) -> WebDriverResult<PauseAction> {
+        let default = Json::U64(0);
+        Ok(PauseAction {
+            duration: try_opt!(body.find("duration").unwrap_or(&default).as_u64(),
+                               ErrorStatus::InvalidArgument,
+                               "Parameter 'duration' was not a positive integer")
+        })
+    }
+}
+
+impl ToJson for PauseAction {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("type".to_owned(),
+                    "pause".to_json());
+        data.insert("duration".to_owned(),
+                    self.duration.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum KeyAction {
+    Up(KeyUpAction),
+    Down(KeyDownAction)
+}
+
+impl Parameters for KeyAction {
+    fn from_json(body: &Json) -> WebDriverResult<KeyAction> {
+        match body.find("type").and_then(|x| x.as_string()) {
+            Some("keyDown") => Ok(KeyAction::Down(try!(KeyDownAction::from_json(body)))),
+            Some("keyUp") => Ok(KeyAction::Up(try!(KeyUpAction::from_json(body)))),
+            Some(_) | None => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                      "Invalid type attribute value for key action"))
+        }
+    }
+}
+
+impl ToJson for KeyAction {
+    fn to_json(&self) -> Json {
+        match self {
+            &KeyAction::Down(ref x) => x.to_json(),
+            &KeyAction::Up(ref x) => x.to_json(),
+        }
+    }
+}
+
+fn validate_key_value(value_str: &str) -> WebDriverResult<String> {
+    let mut graphemes = value_str.graphemes(true);
+    let value = if let Some(g) = graphemes.next() {
+        g
+    } else {
+        return Err(WebDriverError::new(
+            ErrorStatus::InvalidArgument,
+            "Parameter 'value' was an empty string"))
+    };
+    if graphemes.next().is_some() {
+        return Err(WebDriverError::new(
+            ErrorStatus::InvalidArgument,
+            "Parameter 'value' contained multiple graphemes"))
+    };
+    Ok(value.to_string())
+}
+
+#[derive(Debug, PartialEq)]
+pub struct KeyUpAction {
+    pub value: String
 }
 
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct PointerUpAction {
-    pub button: u64,
+impl Parameters for KeyUpAction {
+    fn from_json(body: &Json) -> WebDriverResult<KeyUpAction> {
+        let value_str = try_opt!(
+                try_opt!(body.find("value"),
+                         ErrorStatus::InvalidArgument,
+                         "Missing value parameter").as_string(),
+                ErrorStatus::InvalidArgument,
+            "Parameter 'value' was not a string");
+
+        let value = try!(validate_key_value(value_str));
+        Ok(KeyUpAction {
+            value: value
+        })
+    }
+}
+
+impl ToJson for KeyUpAction {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("type".to_owned(),
+                    "keyUp".to_json());
+        data.insert("value".to_string(),
+                    self.value.to_string().to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct KeyDownAction {
+    pub value: String
 }
 
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+impl Parameters for KeyDownAction {
+    fn from_json(body: &Json) -> WebDriverResult<KeyDownAction> {
+        let value_str = try_opt!(
+                try_opt!(body.find("value"),
+                         ErrorStatus::InvalidArgument,
+                         "Missing value parameter").as_string(),
+                ErrorStatus::InvalidArgument,
+            "Parameter 'value' was not a string");
+        let value = try!(validate_key_value(value_str));
+        Ok(KeyDownAction {
+            value: value
+        })
+    }
+}
+
+impl ToJson for KeyDownAction {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("type".to_owned(),
+                    "keyDown".to_json());
+        data.insert("value".to_owned(),
+                    self.value.to_string().to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
 pub enum PointerOrigin {
-    #[serde(
-        rename = "element-6066-11e4-a52e-4f735466cecf",
-        serialize_with = "serialize_webelement_id",
-        deserialize_with = "deserialize_webelement_id"
-    )]
+    Viewport,
+    Pointer,
     Element(WebElement),
-    #[serde(rename = "pointer")]
-    Pointer,
-    #[serde(rename = "viewport")]
-    Viewport,
+}
+
+impl Parameters for PointerOrigin {
+    fn from_json(body: &Json) -> WebDriverResult<PointerOrigin> {
+        match *body {
+            Json::String(ref x) => {
+                match &**x {
+                    "viewport" => Ok(PointerOrigin::Viewport),
+                    "pointer" => Ok(PointerOrigin::Pointer),
+                    _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                 "Unknown pointer origin"))
+                }
+            },
+            Json::Object(_) => Ok(PointerOrigin::Element(try!(WebElement::from_json(body)))),
+            _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                        "Pointer origin was not a string or an object"))
+        }
+    }
+}
+
+impl ToJson for PointerOrigin {
+    fn to_json(&self) -> Json {
+        match *self {
+            PointerOrigin::Viewport => "viewport".to_json(),
+            PointerOrigin::Pointer => "pointer".to_json(),
+            PointerOrigin::Element(ref x) => x.to_json(),
+        }
+    }
 }
 
 impl Default for PointerOrigin {
     fn default() -> PointerOrigin {
         PointerOrigin::Viewport
     }
 }
 
-fn serialize_webelement_id<S>(element: &WebElement, serializer: S) -> Result<S::Ok, S::Error>
-where
-    S: Serializer,
-{
-    element.id.serialize(serializer)
-}
-
-fn deserialize_webelement_id<'de, D>(deserializer: D) -> Result<WebElement, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    String::deserialize(deserializer).map(|id| WebElement { id })
+#[derive(Debug, PartialEq)]
+pub enum PointerAction {
+    Up(PointerUpAction),
+    Down(PointerDownAction),
+    Move(PointerMoveAction),
+    Cancel
 }
 
-fn deserialize_to_option_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    Option::deserialize(deserializer)?
-        .ok_or_else(|| de::Error::custom("invalid type: null, expected i64"))
-        .map(|v: i64| Some(v))
+impl Parameters for PointerAction {
+    fn from_json(body: &Json) -> WebDriverResult<PointerAction> {
+        match body.find("type").and_then(|x| x.as_string()) {
+            Some("pointerUp") => Ok(PointerAction::Up(try!(PointerUpAction::from_json(body)))),
+            Some("pointerDown") => Ok(PointerAction::Down(try!(PointerDownAction::from_json(body)))),
+            Some("pointerMove") => Ok(PointerAction::Move(try!(PointerMoveAction::from_json(body)))),
+            Some("pointerCancel") => Ok(PointerAction::Cancel),
+            Some(_) | None => Err(WebDriverError::new(
+                ErrorStatus::InvalidArgument,
+                "Missing or invalid type argument for pointer action"))
+        }
+    }
 }
 
-fn deserialize_to_option_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    Option::deserialize(deserializer)?
-        .ok_or_else(|| de::Error::custom("invalid type: null, expected i64"))
-        .map(|v: u64| Some(v))
+impl ToJson for PointerAction {
+    fn to_json(&self) -> Json {
+        match self {
+            &PointerAction::Down(ref x) => x.to_json(),
+            &PointerAction::Up(ref x) => x.to_json(),
+            &PointerAction::Move(ref x) => x.to_json(),
+            &PointerAction::Cancel => {
+                let mut data = BTreeMap::new();
+                data.insert("type".to_owned(),
+                            "pointerCancel".to_json());
+                Json::Object(data)
+            }
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct PointerUpAction {
+    pub button: u64,
 }
 
-#[cfg(test)]
-mod test {
-    use super::*;
-    use serde_json;
-    use test::{check_deserialize, check_serialize_deserialize};
+impl Parameters for PointerUpAction {
+    fn from_json(body: &Json) -> WebDriverResult<PointerUpAction> {
+        let button = try_opt!(
+            try_opt!(body.find("button"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing button parameter").as_u64(),
+            ErrorStatus::InvalidArgument,
+            "Parameter 'button' was not a positive integer");
 
-    #[test]
-    fn test_json_action_sequence_null() {
-        let json = r#"{
-            "id":"none",
-            "type":"none",
-            "actions":[{
-                "type":"pause","duration":1
-            }]
-        }"#;
-        let data = ActionSequence {
-            id: Some("none".into()),
-            actions: ActionsType::Null {
-                actions: vec![NullActionItem::General(GeneralAction::Pause(PauseAction {
-                    duration: 1,
-                }))],
-            },
-        };
-
-        check_serialize_deserialize(&json, &data);
+        Ok(PointerUpAction {
+            button: button
+        })
     }
+}
 
-    #[test]
-    fn test_json_action_sequence_key() {
-        let json = r#"{
-            "id":"some_key",
-            "type":"key",
-            "actions":[
-                {"type":"keyDown","value":"f"}
-            ]
-        }"#;
-        let data = ActionSequence {
-            id: Some("some_key".into()),
-            actions: ActionsType::Key {
-                actions: vec![KeyActionItem::Key(KeyAction::Down(KeyDownAction {
-                    value: String::from("f"),
-                }))],
-            },
-        };
-
-        check_serialize_deserialize(&json, &data);
+impl ToJson for PointerUpAction {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("type".to_owned(),
+                    "pointerUp".to_json());
+        data.insert("button".to_owned(), self.button.to_json());
+        Json::Object(data)
     }
+}
 
-    #[test]
-    fn test_json_action_sequence_pointer() {
-        let json = r#"{
-            "id":"some_pointer",
-            "type":"pointer",
-            "parameters":{
-                "pointerType":"mouse"
-            },
-            "actions":[
-                {"type":"pointerDown","button":0},
-                {"type":"pointerMove","origin":"pointer","x":10,"y":20},
-                {"type":"pointerUp","button":0}
-            ]
-        }"#;
-        let data = ActionSequence {
-            id: Some("some_pointer".into()),
-            actions: ActionsType::Pointer {
-                parameters: PointerActionParameters {
-                    pointer_type: PointerType::Mouse,
-                },
-                actions: vec![
-                    PointerActionItem::Pointer(PointerAction::Down(PointerDownAction {
-                        button: 0,
-                    })),
-                    PointerActionItem::Pointer(PointerAction::Move(PointerMoveAction {
-                        origin: PointerOrigin::Pointer,
-                        duration: None,
-                        x: Some(10),
-                        y: Some(20),
-                    })),
-                    PointerActionItem::Pointer(PointerAction::Up(PointerUpAction { button: 0 })),
-                ],
-            },
-        };
+#[derive(Debug, PartialEq)]
+pub struct PointerDownAction {
+    pub button: u64,
+}
 
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_action_sequence_actions_missing() {
-        let json = r#"{
-            "id": "3"
-        }"#;
-
-        assert!(serde_json::from_str::<ActionSequence>(&json).is_err());
-    }
+impl Parameters for PointerDownAction {
+    fn from_json(body: &Json) -> WebDriverResult<PointerDownAction> {
+        let button = try_opt!(
+            try_opt!(body.find("button"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing button parameter").as_u64(),
+            ErrorStatus::InvalidArgument,
+            "Parameter 'button' was not a positive integer");
 
-    #[test]
-    fn test_json_action_sequence_actions_null() {
-        let json = r#"{
-            "id": "3",
-            "actions": null
-        }"#;
-
-        assert!(serde_json::from_str::<ActionSequence>(&json).is_err());
+        Ok(PointerDownAction {
+            button: button
+        })
     }
-
-    #[test]
-    fn test_json_action_sequence_actions_invalid_type() {
-        let json = r#"{
-            "id": "3",
-            "actions": "foo"
-        }"#;
-
-        assert!(serde_json::from_str::<ActionSequence>(&json).is_err());
-    }
+}
 
-    #[test]
-    fn test_json_actions_type_null() {
-        let json = r#"{
-            "type":"none",
-            "actions":[{
-                "type":"pause",
-                "duration":1
-            }]
-        }"#;
-        let data = ActionsType::Null {
-            actions: vec![NullActionItem::General(GeneralAction::Pause(PauseAction {
-                duration: 1,
-            }))],
-        };
-
-        check_serialize_deserialize(&json, &data);
+impl ToJson for PointerDownAction {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("type".to_owned(),
+                    "pointerDown".to_json());
+        data.insert("button".to_owned(), self.button.to_json());
+        Json::Object(data)
     }
+}
 
-    #[test]
-    fn test_json_actions_type_key() {
-        let json = r#"{
-            "type":"key",
-            "actions":[{
-                "type":"keyDown",
-                "value":"f"
-            }]
-        }"#;
-        let data = ActionsType::Key {
-            actions: vec![KeyActionItem::Key(KeyAction::Down(KeyDownAction {
-                value: String::from("f"),
-            }))],
-        };
+#[derive(Debug, PartialEq)]
+pub struct PointerMoveAction {
+    pub duration: Nullable<u64>,
+    pub origin: PointerOrigin,
+    pub x: Nullable<i64>,
+    pub y: Nullable<i64>
+}
 
-        check_serialize_deserialize(&json, &data);
-    }
+impl Parameters for PointerMoveAction {
+    fn from_json(body: &Json) -> WebDriverResult<PointerMoveAction> {
+        let duration = match body.find("duration") {
+            Some(duration) => Some(try_opt!(duration.as_u64(),
+                                            ErrorStatus::InvalidArgument,
+                                            "Parameter 'duration' was not a positive integer")),
+            None => None
 
-    #[test]
-    fn test_json_actions_type_pointer() {
-        let json = r#"{
-            "type":"pointer",
-            "parameters":{"pointerType":"mouse"},
-            "actions":[
-                {"type":"pointerDown","button":1}
-            ]}"#;
-        let data = ActionsType::Pointer {
-            parameters: PointerActionParameters {
-                pointer_type: PointerType::Mouse,
-            },
-            actions: vec![PointerActionItem::Pointer(PointerAction::Down(
-                PointerDownAction { button: 1 },
-            ))],
         };
 
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_actions_type_invalid() {
-        let json = r#"{"actions":[{"foo":"bar"}]}"#;
-        assert!(serde_json::from_str::<ActionsType>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_null_action_item_general() {
-        let json = r#"{"type":"pause","duration":1}"#;
-        let data = NullActionItem::General(GeneralAction::Pause(PauseAction { duration: 1 }));
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_null_action_item_invalid_type() {
-        let json = r#"{"type":"invalid"}"#;
-        assert!(serde_json::from_str::<NullActionItem>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_general_action_pause() {
-        let json = r#"{"type":"pause","duration":1}"#;
-        let data = GeneralAction::Pause(PauseAction { duration: 1 });
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_general_action_pause_with_duration_missing() {
-        let json = r#"{"type":"pause"}"#;
-
-        assert!(serde_json::from_str::<GeneralAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_general_action_pause_with_duration_null() {
-        let json = r#"{"type":"pause","duration":null}"#;
-
-        assert!(serde_json::from_str::<GeneralAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_general_action_pause_with_duration_invalid_type() {
-        let json = r#"{"type":"pause","duration":"foo"}"#;
-
-        assert!(serde_json::from_str::<GeneralAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_general_action_pause_with_duration_negative() {
-        let json = r#"{"type":"pause","duration":-30}"#;
-
-        assert!(serde_json::from_str::<GeneralAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_key_action_item_general() {
-        let json = r#"{"type":"pause","duration":1}"#;
-        let data = KeyActionItem::General(GeneralAction::Pause(PauseAction { duration: 1 }));
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_key_action_item_key() {
-        let json = r#"{"type":"keyDown","value":"f"}"#;
-        let data = KeyActionItem::Key(KeyAction::Down(KeyDownAction {
-            value: String::from("f"),
-        }));
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_key_action_item_invalid_type() {
-        let json = r#"{"type":"invalid"}"#;
-        assert!(serde_json::from_str::<KeyActionItem>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_key_action_missing_subtype() {
-        let json = r#"{"value":"f"}"#;
-
-        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_key_action_wrong_subtype() {
-        let json = r#"{"type":"pause","value":"f"}"#;
-
-        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_key_action_down() {
-        let json = r#"{"type":"keyDown","value":"f"}"#;
-        let data = KeyAction::Down(KeyDownAction {
-            value: "f".to_owned(),
-        });
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_key_action_down_with_value_unicode() {
-        let json = r#"{"type":"keyDown","value":"à"}"#;
-        let data = KeyAction::Down(KeyDownAction {
-            value: "à".to_owned(),
-        });
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_key_action_down_with_value_unicode_encoded() {
-        let json = r#"{"type":"keyDown","value":"\u00E0"}"#;
-        let data = KeyAction::Down(KeyDownAction {
-            value: "à".to_owned(),
-        });
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_key_action_down_with_value_missing() {
-        let json = r#"{"type":"keyDown"}"#;
-
-        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_key_action_down_with_value_null() {
-        let json = r#"{"type":"keyDown","value":null}"#;
-
-        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_key_action_down_with_value_invalid_type() {
-        let json = r#"{"type":"keyDown,"value":["f","o","o"]}"#;
-
-        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_key_action_down_with_multiple_code_points() {
-        let json = r#"{"type":"keyDown","value":"fo"}"#;
-
-        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_key_action_up() {
-        let json = r#"{"type":"keyUp","value":"f"}"#;
-        let data = KeyAction::Up(KeyUpAction {
-            value: "f".to_owned(),
-        });
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_key_action_up_with_value_unicode() {
-        let json = r#"{"type":"keyUp","value":"à"}"#;
-        let data = KeyAction::Up(KeyUpAction {
-            value: "à".to_owned(),
-        });
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_key_action_up_with_value_unicode_encoded() {
-        let json = r#"{"type":"keyUp","value":"\u00E0"}"#;
-        let data = KeyAction::Up(KeyUpAction {
-            value: "à".to_owned(),
-        });
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_key_action_up_with_value_missing() {
-        let json = r#"{"type":"keyUp"}"#;
-
-        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_key_action_up_with_value_null() {
-        let json = r#"{"type":"keyUp,"value":null}"#;
-
-        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_key_action_up_with_value_invalid_type() {
-        let json = r#"{"type":"keyUp,"value":["f","o","o"]}"#;
-
-        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_key_action_up_with_multiple_code_points() {
-        let json = r#"{"type":"keyUp","value":"fo"}"#;
-
-        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_item_general() {
-        let json = r#"{"type":"pause","duration":1}"#;
-        let data = PointerActionItem::General(GeneralAction::Pause(PauseAction { duration: 1 }));
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_item_pointer() {
-        let json = r#"{"type":"pointerCancel"}"#;
-        let data = PointerActionItem::Pointer(PointerAction::Cancel);
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_item_invalid() {
-        let json = r#"{"type":"invalid"}"#;
-
-        assert!(serde_json::from_str::<PointerActionItem>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_parameters_mouse() {
-        let json = r#"{"pointerType":"mouse"}"#;
-        let data = PointerActionParameters {
-            pointer_type: PointerType::Mouse,
+        let origin = match body.find("origin") {
+            Some(o) => try!(PointerOrigin::from_json(o)),
+            None => PointerOrigin::default()
         };
 
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_parameters_pen() {
-        let json = r#"{"pointerType":"pen"}"#;
-        let data = PointerActionParameters {
-            pointer_type: PointerType::Pen,
+        let x = match body.find("x") {
+            Some(x) => {
+                Some(try_opt!(x.as_i64(),
+                              ErrorStatus::InvalidArgument,
+                              "Parameter 'x' was not an integer"))
+            },
+            None => None
         };
 
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_parameters_touch() {
-        let json = r#"{"pointerType":"touch"}"#;
-        let data = PointerActionParameters {
-            pointer_type: PointerType::Touch,
+        let y = match body.find("y") {
+            Some(y) => {
+                Some(try_opt!(y.as_i64(),
+                              ErrorStatus::InvalidArgument,
+                              "Parameter 'y' was not an integer"))
+            },
+            None => None
         };
 
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_item_invalid_type() {
-        let json = r#"{"type":"pointerInvalid"}"#;
-        assert!(serde_json::from_str::<PointerActionItem>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_with_subtype_missing() {
-        let json = r#"{"button":1}"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_with_subtype_invalid() {
-        let json = r#"{"type":"invalid"}"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_with_subtype_wrong() {
-        let json = r#"{"type":"pointerMove",button":1}"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_cancel() {
-        let json = r#"{"type":"pointerCancel"}"#;
-        let data = PointerAction::Cancel;
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_down() {
-        let json = r#"{"type":"pointerDown","button":1}"#;
-        let data = PointerAction::Down(PointerDownAction { button: 1 });
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_down_with_button_missing() {
-        let json = r#"{"type":"pointerDown"}"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_down_with_button_null() {
-        let json = r#"{
-            "type":"pointerDown",
-            "button":null
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_down_with_button_invalid_type() {
-        let json = r#"{
-            "type":"pointerDown",
-            "button":"foo",
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_down_with_button_negative() {
-        let json = r#"{
-            "type":"pointerDown",
-            "button":-30
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_move() {
-        let json = r#"{
-            "type":"pointerMove",
-            "duration":100,
-            "origin":"viewport",
-            "x":5,
-            "y":10
-        }"#;
-        let data = PointerAction::Move(PointerMoveAction {
-            duration: Some(100),
-            origin: PointerOrigin::Viewport,
-            x: Some(5),
-            y: Some(10),
-        });
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_missing_subtype() {
-        let json = r#"{
-            "duration":100,
-            "origin":"viewport",
-            "x":5,
-            "y":10
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_wrong_subtype() {
-        let json = r#"{
-            "type":"pointerUp",
-            "duration":100,
-            "origin":"viewport",
-            "x":5,
-            "y":10
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_with_duration_missing() {
-        let json = r#"{
-            "type":"pointerMove",
-            "origin":"viewport",
-            "x":5,
-            "y":10
-        }"#;
-        let data = PointerAction::Move(PointerMoveAction {
-            duration: None,
-            origin: PointerOrigin::Viewport,
-            x: Some(5),
-            y: Some(10),
-        });
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_with_duration_null() {
-        let json = r#"{
-            "type":"pointerMove",
-            "duration":null,
-            "origin":"viewport",
-            "x":5,
-            "y":10
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_with_duration_invalid_type() {
-        let json = r#"{
-            "type":"pointerMove",
-            "duration":"invalid",
-            "origin":"viewport",
-            "x":5,
-            "y":10
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_with_duration_negative() {
-        let json = r#"{
-            "type":"pointerMove",
-            "duration":-30,
-            "origin":"viewport",
-            "x":5,
-            "y":10
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_with_origin_missing() {
-        let json = r#"{
-            "type":"pointerMove",
-            "duration":100,
-            "x":5,
-            "y":10
-        }"#;
-        let data = PointerAction::Move(PointerMoveAction {
-            duration: Some(100),
-            origin: PointerOrigin::Viewport,
-            x: Some(5),
-            y: Some(10),
-        });
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_with_x_missing() {
-        let json = r#"{
-            "type":"pointerMove",
-            "duration":100,
-            "origin":"viewport",
-            "y":10
-        }"#;
-        let data = PointerAction::Move(PointerMoveAction {
-            duration: Some(100),
-            origin: PointerOrigin::Viewport,
-            x: None,
-            y: Some(10),
-        });
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_with_x_null() {
-        let json = r#"{
-            "type":"pointerMove",
-            "duration":100,
-            "origin":"viewport",
-            "x": null,
-            "y":10
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_with_x_invalid_type() {
-        let json = r#"{
-            "type":"pointerMove",
-            "duration":100,
-            "origin":"viewport",
-            "x": "invalid",
-            "y":10
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_with_y_missing() {
-        let json = r#"{
-            "type":"pointerMove",
-            "duration":100,
-            "origin":"viewport",
-            "x":5
-        }"#;
-        let data = PointerAction::Move(PointerMoveAction {
-            duration: Some(100),
-            origin: PointerOrigin::Viewport,
-            x: Some(5),
-            y: None,
-        });
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_with_y_null() {
-        let json = r#"{
-            "type":"pointerMove",
-            "duration":100,
-            "origin":"viewport",
-            "x":5,
-            "y":null
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_move_with_y_invalid_type() {
-        let json = r#"{
-            "type":"pointerMove",
-            "duration":100,
-            "origin":"viewport",
-            "x":5,
-            "y":"invalid"
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_up() {
-        let json = r#"{
-            "type":"pointerUp",
-            "button":1
-        }"#;
-        let data = PointerAction::Up(PointerUpAction { button: 1 });
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_action_up_with_button_missing() {
-        let json = r#"{
-            "type":"pointerUp"
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_up_with_button_null() {
-        let json = r#"{
-            "type":"pointerUp",
-            "button":null
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_up_with_button_invalid_type() {
-        let json = r#"{
-            "type":"pointerUp",
-            "button":"foo",
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_action_up_with_button_negative() {
-        let json = r#"{
-            "type":"pointerUp",
-            "button":-30
-        }"#;
-
-        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_origin_pointer() {
-        let json = r#""pointer""#;
-        let data = PointerOrigin::Pointer;
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_origin_viewport() {
-        let json = r#""viewport""#;
-        let data = PointerOrigin::Viewport;
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_origin_web_element() {
-        let json = r#"{"element-6066-11e4-a52e-4f735466cecf":"elem"}"#;
-        let data = PointerOrigin::Element(WebElement { id: "elem".into() });
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_origin_invalid_type() {
-        let data = r#""invalid""#;
-        assert!(serde_json::from_str::<PointerOrigin>(&data).is_err());
-    }
-
-    #[test]
-    fn test_json_pointer_type_mouse() {
-        let json = r#""mouse""#;
-        let data = PointerType::Mouse;
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_type_pen() {
-        let json = r#""pen""#;
-        let data = PointerType::Pen;
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_type_touch() {
-        let json = r#""touch""#;
-        let data = PointerType::Touch;
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_pointer_type_invalid_type() {
-        let json = r#""invalid""#;
-        assert!(serde_json::from_str::<PointerType>(&json).is_err());
+        Ok(PointerMoveAction {
+            duration: duration.into(),
+            origin: origin.into(),
+            x: x.into(),
+            y: y.into(),
+        })
     }
 }
+
+impl ToJson for PointerMoveAction {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("type".to_owned(), "pointerMove".to_json());
+        if self.duration.is_value() {
+            data.insert("duration".to_owned(),
+                        self.duration.to_json());
+        }
+
+        data.insert("origin".to_owned(), self.origin.to_json());
+
+        if self.x.is_value() {
+            data.insert("x".to_owned(), self.x.to_json());
+        }
+        if self.y.is_value() {
+            data.insert("y".to_owned(), self.y.to_json());
+        }
+        Json::Object(data)
+    }
+}
--- a/testing/webdriver/src/capabilities.rs
+++ b/testing/webdriver/src/capabilities.rs
@@ -1,14 +1,15 @@
+use command::Parameters;
 use error::{ErrorStatus, WebDriverError, WebDriverResult};
-use serde_json::{Map, Value};
-use std::convert::From;
+use rustc_serialize::json::{Json, ToJson};
+use std::collections::BTreeMap;
 use url::Url;
 
-pub type Capabilities = Map<String, Value>;
+pub type Capabilities = BTreeMap<String, Json>;
 
 /// Trait for objects that can be used to inspect browser capabilities
 ///
 /// The main methods in this trait are called with a Capabilites object
 /// resulting from a full set of potential capabilites for the session.  Given
 /// those Capabilities they return a property of the browser instance that
 /// would be initiated. In many cases this will be independent of the input,
 /// but in the case of e.g. browser version, it might depend on a path to the
@@ -40,86 +41,69 @@ pub trait BrowserCapabilities {
     fn accept_insecure_certs(&mut self, &Capabilities) -> WebDriverResult<bool>;
 
     /// Indicates whether driver supports all of the window resizing and
     /// repositioning commands.
     fn set_window_rect(&mut self, &Capabilities) -> WebDriverResult<bool>;
 
     fn accept_proxy(
         &mut self,
-        proxy_settings: &Map<String, Value>,
+        proxy_settings: &BTreeMap<String, Json>,
         &Capabilities,
     ) -> WebDriverResult<bool>;
 
     /// Type check custom properties
     ///
     /// Check that custom properties containing ":" have the correct data types.
     /// Properties that are unrecognised must be ignored i.e. return without
     /// error.
-    fn validate_custom(&self, name: &str, value: &Value) -> WebDriverResult<()>;
+    fn validate_custom(&self, name: &str, value: &Json) -> WebDriverResult<()>;
 
     /// Check if custom properties are accepted capabilites
     ///
     /// Check that custom properties containing ":" are compatible with
     /// the implementation.
     fn accept_custom(
         &mut self,
         name: &str,
-        value: &Value,
+        value: &Json,
         merged: &Capabilities,
     ) -> WebDriverResult<bool>;
 }
 
 /// Trait to abstract over various version of the new session parameters
 ///
 /// This trait is expected to be implemented on objects holding the capabilities
 /// from a new session command.
 pub trait CapabilitiesMatching {
     /// Match the BrowserCapabilities against some candidate capabilites
     ///
     /// Takes a BrowserCapabilites object and returns a set of capabilites that
     /// are valid for that browser, if any, or None if there are no matching
     /// capabilities.
-    fn match_browser<T: BrowserCapabilities>(
-        &self,
-        browser_capabilities: &mut T,
-    ) -> WebDriverResult<Option<Capabilities>>;
+    fn match_browser<T: BrowserCapabilities>(&self, browser_capabilities: &mut T)
+                                             -> WebDriverResult<Option<Capabilities>>;
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq)]
 pub struct SpecNewSessionParameters {
-    #[serde(default = "Capabilities::default")]
     pub alwaysMatch: Capabilities,
-    #[serde(default = "firstMatch_default")]
     pub firstMatch: Vec<Capabilities>,
 }
 
-impl Default for SpecNewSessionParameters {
-    fn default() -> Self {
-        SpecNewSessionParameters {
-            alwaysMatch: Capabilities::new(),
-            firstMatch: vec![Capabilities::new()],
-        }
-    }
-}
-
-fn firstMatch_default() -> Vec<Capabilities> {
-    vec![Capabilities::default()]
-}
-
 impl SpecNewSessionParameters {
     fn validate<T: BrowserCapabilities>(
         &self,
         mut capabilities: Capabilities,
         browser_capabilities: &T,
     ) -> WebDriverResult<Capabilities> {
         // Filter out entries with the value `null`
         let null_entries = capabilities
             .iter()
-            .filter(|&(_, ref value)| **value == Value::Null)
+            .filter(|&(_, ref value)| **value == Json::Null)
             .map(|(k, _)| k.clone())
             .collect::<Vec<String>>();
         for key in null_entries {
             capabilities.remove(&key);
         }
 
         for (key, value) in capabilities.iter() {
             match &**key {
@@ -156,169 +140,150 @@ impl SpecNewSessionParameters {
                         browser_capabilities.validate_custom(x, value)?
                     }
                 }
             }
         }
         Ok(capabilities)
     }
 
-    fn validate_page_load_strategy(value: &Value) -> WebDriverResult<()> {
+    fn validate_page_load_strategy(value: &Json) -> WebDriverResult<()> {
         match value {
-            &Value::String(ref x) => match &**x {
-                "normal" | "eager" | "none" => {}
-                x => {
-                    return Err(WebDriverError::new(
-                        ErrorStatus::InvalidArgument,
-                        format!("Invalid page load strategy: {}", x),
-                    ))
+            &Json::String(ref x) => {
+                match &**x {
+                    "normal" |
+                    "eager" |
+                    "none" => {},
+                    x => {
+                        return Err(WebDriverError::new(
+                            ErrorStatus::InvalidArgument,
+                            format!("Invalid page load strategy: {}", x)))
+                    }
                 }
-            },
-            _ => {
-                return Err(WebDriverError::new(
-                    ErrorStatus::InvalidArgument,
-                    "pageLoadStrategy is not a string",
-                ))
             }
+            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                "pageLoadStrategy is not a string"))
         }
         Ok(())
     }
 
-    fn validate_proxy(proxy_value: &Value) -> WebDriverResult<()> {
-        let obj = try_opt!(
-            proxy_value.as_object(),
-            ErrorStatus::InvalidArgument,
-            "proxy is not an object"
-        );
+    fn validate_proxy(proxy_value: &Json) -> WebDriverResult<()> {
+        let obj = try_opt!(proxy_value.as_object(),
+                           ErrorStatus::InvalidArgument,
+                           "proxy is not an object");
 
         for (key, value) in obj.iter() {
             match &**key {
-                "proxyType" => match value.as_str() {
-                    Some("pac") | Some("direct") | Some("autodetect") | Some("system")
-                    | Some("manual") => {}
-                    Some(x) => {
-                        return Err(WebDriverError::new(
-                            ErrorStatus::InvalidArgument,
-                            format!("Invalid proxyType value: {}", x),
-                        ))
-                    }
-                    None => {
-                        return Err(WebDriverError::new(
-                            ErrorStatus::InvalidArgument,
-                            format!("proxyType is not a string: {}", value),
-                        ))
-                    }
+                "proxyType" => match value.as_string() {
+                    Some("pac") |
+                    Some("direct") |
+                    Some("autodetect") |
+                    Some("system") |
+                    Some("manual") => {},
+                    Some(x) => return Err(WebDriverError::new(
+                        ErrorStatus::InvalidArgument,
+                        format!("Invalid proxyType value: {}", x))),
+                    None => return Err(WebDriverError::new(
+                        ErrorStatus::InvalidArgument,
+                        format!("proxyType is not a string: {}", value))),
                 },
 
-                "proxyAutoconfigUrl" => match value.as_str() {
+                "proxyAutoconfigUrl" => match value.as_string() {
                     Some(x) => {
                         Url::parse(x).or(Err(WebDriverError::new(
                             ErrorStatus::InvalidArgument,
-                            format!("proxyAutoconfigUrl is not a valid URL: {}", x),
-                        )))?;
-                    }
-                    None => {
-                        return Err(WebDriverError::new(
-                            ErrorStatus::InvalidArgument,
-                            "proxyAutoconfigUrl is not a string",
-                        ))
-                    }
+                            format!("proxyAutoconfigUrl is not a valid URL: {}", x))))?;
+                    },
+                    None => return Err(WebDriverError::new(
+                        ErrorStatus::InvalidArgument,
+                        "proxyAutoconfigUrl is not a string"
+                    ))
                 },
 
                 "ftpProxy" => SpecNewSessionParameters::validate_host(value, "ftpProxy")?,
                 "httpProxy" => SpecNewSessionParameters::validate_host(value, "httpProxy")?,
                 "noProxy" => SpecNewSessionParameters::validate_no_proxy(value)?,
                 "sslProxy" => SpecNewSessionParameters::validate_host(value, "sslProxy")?,
                 "socksProxy" => SpecNewSessionParameters::validate_host(value, "socksProxy")?,
                 "socksVersion" => if !value.is_number() {
                     return Err(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
-                        format!("socksVersion is not a number: {}", value),
-                    ));
+                        format!("socksVersion is not a number: {}", value)
+                    ))
                 },
 
-                x => {
-                    return Err(WebDriverError::new(
-                        ErrorStatus::InvalidArgument,
-                        format!("Invalid proxy configuration entry: {}", x),
-                    ))
-                }
+                x => return Err(WebDriverError::new(
+                    ErrorStatus::InvalidArgument,
+                    format!("Invalid proxy configuration entry: {}", x)))
             }
         }
 
         Ok(())
     }
 
-    fn validate_no_proxy(value: &Value) -> WebDriverResult<()> {
+    fn validate_no_proxy(value: &Json) -> WebDriverResult<()> {
         match value.as_array() {
             Some(hosts) => {
                 for host in hosts {
-                    match host.as_str() {
-                        Some(_) => {}
-                        None => {
-                            return Err(WebDriverError::new(
-                                ErrorStatus::InvalidArgument,
-                                format!("noProxy item is not a string: {}", host),
-                            ))
-                        }
+                    match host.as_string() {
+                        Some(_) => {},
+                        None => return Err(WebDriverError::new(
+                            ErrorStatus::InvalidArgument,
+                            format!("noProxy item is not a string: {}", host)
+                        ))
                     }
                 }
-            }
-            None => {
-                return Err(WebDriverError::new(
-                    ErrorStatus::InvalidArgument,
-                    format!("noProxy is not an array: {}", value),
-                ))
-            }
+            },
+            None => return Err(WebDriverError::new(
+                ErrorStatus::InvalidArgument,
+                format!("noProxy is not an array: {}", value)
+            ))
         }
 
         Ok(())
     }
 
     /// Validate whether a named capability is JSON value is a string containing a host
     /// and possible port
-    fn validate_host(value: &Value, entry: &str) -> WebDriverResult<()> {
-        match value.as_str() {
+    fn validate_host(value: &Json, entry: &str) -> WebDriverResult<()> {
+        match value.as_string() {
             Some(host) => {
                 if host.contains("://") {
                     return Err(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
-                        format!("{} must not contain a scheme: {}", entry, host),
-                    ));
+                        format!("{} must not contain a scheme: {}", entry, host)));
                 }
 
                 // Temporarily add a scheme so the host can be parsed as URL
                 let s = String::from(format!("http://{}", host));
                 let url = Url::parse(s.as_str()).or(Err(WebDriverError::new(
                     ErrorStatus::InvalidArgument,
-                    format!("{} is not a valid URL: {}", entry, host),
-                )))?;
+                    format!("{} is not a valid URL: {}", entry, host))))?;
 
-                if url.username() != "" || url.password() != None || url.path() != "/"
-                    || url.query() != None || url.fragment() != None
-                {
-                    return Err(WebDriverError::new(
-                        ErrorStatus::InvalidArgument,
-                        format!("{} is not of the form host[:port]: {}", entry, host),
-                    ));
-                }
-            }
+                if url.username() != "" ||
+                    url.password() != None ||
+                    url.path() != "/" ||
+                    url.query() != None ||
+                    url.fragment() != None {
+                        return Err(WebDriverError::new(
+                            ErrorStatus::InvalidArgument,
+                            format!("{} is not of the form host[:port]: {}", entry, host)));
+                    }
+            },
 
-            None => {
-                return Err(WebDriverError::new(
-                    ErrorStatus::InvalidArgument,
-                    format!("{} is not a string: {}", entry, value),
-                ))
-            }
+            None => return Err(WebDriverError::new(
+                ErrorStatus::InvalidArgument,
+                format!("{} is not a string: {}", entry, value)
+            ))
         }
 
         Ok(())
     }
 
-    fn validate_timeouts(value: &Value) -> WebDriverResult<()> {
+    fn validate_timeouts(value: &Json) -> WebDriverResult<()> {
         let obj = try_opt!(
             value.as_object(),
             ErrorStatus::InvalidArgument,
             "timeouts capability is not an object"
         );
 
         for (key, value) in obj.iter() {
             match &**key {
@@ -343,134 +308,197 @@ impl SpecNewSessionParameters {
                     ))
                 }
             }
         }
 
         Ok(())
     }
 
-    fn validate_unhandled_prompt_behaviour(value: &Value) -> WebDriverResult<()> {
+    fn validate_unhandled_prompt_behaviour(value: &Json) -> WebDriverResult<()> {
         let behaviour = try_opt!(
-            value.as_str(),
+            value.as_string(),
             ErrorStatus::InvalidArgument,
             format!("unhandledPromptBehavior is not a string: {}", value)
         );
 
         match behaviour {
-            "accept" | "accept and notify" | "dismiss" | "dismiss and notify" | "ignore" => {}
+            "accept" |
+            "accept and notify" |
+            "dismiss" |
+            "dismiss and notify" |
+            "ignore" => {},
             x => {
                 return Err(WebDriverError::new(
                     ErrorStatus::InvalidArgument,
                     format!("Invalid unhandledPromptBehavior value: {}", x),
                 ))
             }
         }
 
         Ok(())
     }
 }
 
+impl Parameters for SpecNewSessionParameters {
+    fn from_json(body: &Json) -> WebDriverResult<SpecNewSessionParameters> {
+        let data = try_opt!(
+            body.as_object(),
+            ErrorStatus::UnknownError,
+            format!("Malformed capabilities, message body is not an object: {}", body)
+        );
+
+        let capabilities = try_opt!(
+            try_opt!(
+                data.get("capabilities"),
+                ErrorStatus::InvalidArgument,
+                "Malformed capabilities, missing \"capabilities\" field"
+            ).as_object(),
+            ErrorStatus::InvalidArgument,
+            "Malformed capabilities, \"capabilities\" field is not an object}"
+        );
+
+        let default_always_match = Json::Object(Capabilities::new());
+        let always_match = try_opt!(
+            capabilities
+                .get("alwaysMatch")
+                .unwrap_or(&default_always_match)
+                .as_object(),
+            ErrorStatus::InvalidArgument,
+            "Malformed capabilities, alwaysMatch field is not an object"
+        );
+        let default_first_matches = Json::Array(vec![]);
+        let first_matches = try_opt!(
+            capabilities
+                .get("firstMatch")
+                .unwrap_or(&default_first_matches)
+                .as_array(),
+            ErrorStatus::InvalidArgument,
+            "Malformed capabilities, firstMatch field is not an array"
+        ).iter()
+            .map(|x| {
+                x.as_object().map(|x| x.clone()).ok_or(WebDriverError::new(
+                    ErrorStatus::InvalidArgument,
+                    "Malformed capabilities, firstMatch entry is not an object",
+                ))
+            })
+            .collect::<WebDriverResult<Vec<Capabilities>>>()?;
+
+        return Ok(SpecNewSessionParameters {
+            alwaysMatch: always_match.clone(),
+            firstMatch: first_matches,
+        });
+    }
+}
+
+impl ToJson for SpecNewSessionParameters {
+    fn to_json(&self) -> Json {
+        let mut body = BTreeMap::new();
+        let mut capabilities = BTreeMap::new();
+        capabilities.insert("alwaysMatch".into(), self.alwaysMatch.to_json());
+        capabilities.insert("firstMatch".into(), self.firstMatch.to_json());
+        body.insert("capabilities".into(), capabilities.to_json());
+        Json::Object(body)
+    }
+}
+
 impl CapabilitiesMatching for SpecNewSessionParameters {
     fn match_browser<T: BrowserCapabilities>(
         &self,
         browser_capabilities: &mut T,
     ) -> WebDriverResult<Option<Capabilities>> {
-        let default = vec![Map::new()];
+        let default = vec![BTreeMap::new()];
         let capabilities_list = if self.firstMatch.len() > 0 {
             &self.firstMatch
         } else {
             &default
         };
 
         let merged_capabilities = capabilities_list
             .iter()
             .map(|first_match_entry| {
-                if first_match_entry
-                    .keys()
-                    .any(|k| self.alwaysMatch.contains_key(k))
-                {
+                if first_match_entry.keys().any(|k| self.alwaysMatch.contains_key(k)) {
                     return Err(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
                         "firstMatch key shadowed a value in alwaysMatch",
                     ));
                 }
                 let mut merged = self.alwaysMatch.clone();
-                for (key, value) in first_match_entry.clone().into_iter() {
-                    merged.insert(key, value);
-                }
+                merged.append(&mut first_match_entry.clone());
                 Ok(merged)
             })
-            .map(|merged| merged.and_then(|x| self.validate(x, browser_capabilities)))
+            .map(|merged| {
+                merged.and_then(|x| self.validate(x, browser_capabilities))
+            })
             .collect::<WebDriverResult<Vec<Capabilities>>>()?;
 
         let selected = merged_capabilities
             .iter()
             .filter_map(|merged| {
                 browser_capabilities.init(merged);
 
                 for (key, value) in merged.iter() {
                     match &**key {
                         "browserName" => {
                             let browserValue = browser_capabilities
                                 .browser_name(merged)
                                 .ok()
                                 .and_then(|x| x);
 
-                            if value.as_str() != browserValue.as_ref().map(|x| &**x) {
+                            if value.as_string() != browserValue.as_ref().map(|x| &**x) {
                                 return None;
                             }
                         }
                         "browserVersion" => {
                             let browserValue = browser_capabilities
                                 .browser_version(merged)
                                 .ok()
                                 .and_then(|x| x);
                             // We already validated this was a string
-                            let version_cond = value.as_str().unwrap_or("");
+                            let version_cond = value.as_string().unwrap_or("");
                             if let Some(version) = browserValue {
                                 if !browser_capabilities
                                     .compare_browser_version(&*version, version_cond)
                                     .unwrap_or(false)
                                 {
                                     return None;
                                 }
                             } else {
                                 return None;
                             }
                         }
                         "platformName" => {
                             let browserValue = browser_capabilities
                                 .platform_name(merged)
                                 .ok()
                                 .and_then(|x| x);
-                            if value.as_str() != browserValue.as_ref().map(|x| &**x) {
+                            if value.as_string() != browserValue.as_ref().map(|x| &**x) {
                                 return None;
                             }
                         }
                         "acceptInsecureCerts" => {
-                            if value.as_bool().unwrap_or(false)
-                                && !browser_capabilities
+                            if value.as_boolean().unwrap_or(false) &&
+                                !browser_capabilities
                                     .accept_insecure_certs(merged)
                                     .unwrap_or(false)
                             {
                                 return None;
                             }
                         }
                         "setWindowRect" => {
-                            if value.as_bool().unwrap_or(false)
-                                && !browser_capabilities
+                            if value.as_boolean().unwrap_or(false) &&
+                                !browser_capabilities
                                     .set_window_rect(merged)
                                     .unwrap_or(false)
                             {
                                 return None;
                             }
                         }
                         "proxy" => {
-                            let default = Map::new();
+                            let default = BTreeMap::new();
                             let proxy = value.as_object().unwrap_or(&default);
                             if !browser_capabilities
                                 .accept_proxy(&proxy, merged)
                                 .unwrap_or(false)
                             {
                                 return None;
                             }
                         }
@@ -492,192 +520,94 @@ impl CapabilitiesMatching for SpecNewSes
                 return Some(merged);
             })
             .next()
             .map(|x| x.clone());
         Ok(selected)
     }
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq)]
 pub struct LegacyNewSessionParameters {
-    #[serde(default = "Capabilities::default")]
     pub desired: Capabilities,
-    #[serde(default = "Capabilities::default")]
     pub required: Capabilities,
 }
 
 impl CapabilitiesMatching for LegacyNewSessionParameters {
     fn match_browser<T: BrowserCapabilities>(
         &self,
         browser_capabilities: &mut T,
     ) -> WebDriverResult<Option<Capabilities>> {
         // For now don't do anything much, just merge the
         // desired and required and return the merged list.
 
-        let mut capabilities: Capabilities = Map::new();
+        let mut capabilities: Capabilities = BTreeMap::new();
         self.required.iter().chain(self.desired.iter()).fold(
             &mut capabilities,
             |caps, (key, value)| {
                 if !caps.contains_key(key) {
                     caps.insert(key.clone(), value.clone());
                 }
                 caps
             },
         );
         browser_capabilities.init(&capabilities);
         Ok(Some(capabilities))
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use serde_json::{self, Value};
-    use test::check_deserialize;
-
-    fn validate_proxy(value: &str) -> WebDriverResult<()> {
-        let data = serde_json::from_str::<Value>(value).unwrap();
-        SpecNewSessionParameters::validate_proxy(&data)
-    }
-
-    #[test]
-    fn test_json_spec_new_session_parameters_alwaysMatch_only() {
-        let json = r#"{
-            "alwaysMatch":{}
-        }"#;
-        let data = SpecNewSessionParameters {
-            alwaysMatch: Capabilities::new(),
-            firstMatch: vec![Capabilities::new()],
-        };
-
-        check_deserialize(&json, &data);
-    }
+impl Parameters for LegacyNewSessionParameters {
+    fn from_json(body: &Json) -> WebDriverResult<LegacyNewSessionParameters> {
+        let data = try_opt!(
+            body.as_object(),
+            ErrorStatus::UnknownError,
+            format!("Malformed legacy capabilities, message body is not an object: {}", body)
+        );
 
-    #[test]
-    fn test_json_spec_new_session_parameters_firstMatch_only() {
-        let json = r#"{
-            "firstMatch":[{}]
-        }"#;
-        let data = SpecNewSessionParameters {
-            alwaysMatch: Capabilities::new(),
-            firstMatch: vec![Capabilities::new()],
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_spec_new_session_parameters_alwaysMatch_null() {
-        let json = r#"{
-            "alwaysMatch":null,
-            "firstMatch":[{}]
-        }"#;
-
-        assert!(serde_json::from_str::<SpecNewSessionParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_spec_new_session_parameters_firstMatch_null() {
-        let json = r#"{
-            "alwaysMatch":{},
-            "firstMatch":null
-        }"#;
-
-        assert!(serde_json::from_str::<SpecNewSessionParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_spec_new_session_parameters_both_empty() {
-        let json = r#"{
-            "alwaysMatch":{},
-            "firstMatch":[{}]
-        }"#;
-        let data = SpecNewSessionParameters {
-            alwaysMatch: Capabilities::new(),
-            firstMatch: vec![Capabilities::new()],
+        let desired = if let Some(capabilities) = data.get("desiredCapabilities") {
+            try_opt!(
+                capabilities.as_object(),
+                ErrorStatus::InvalidArgument,
+                "Malformed legacy capabilities, desiredCapabilities field is not an object"
+            ).clone()
+        } else {
+            BTreeMap::new()
         };
 
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_spec_new_session_parameters_both_with_capability() {
-        let json = r#"{
-            "alwaysMatch":{"foo":"bar"},
-            "firstMatch":[{"foo2":"bar2"}]
-        }"#;
-        let mut data = SpecNewSessionParameters {
-            alwaysMatch: Capabilities::new(),
-            firstMatch: vec![Capabilities::new()],
-        };
-        data.alwaysMatch.insert("foo".into(), "bar".into());
-        data.firstMatch[0].insert("foo2".into(), "bar2".into());
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_spec_legacy_new_session_parameters_desired_only() {
-        let json = r#"{"desired":{}}"#;
-        let data = LegacyNewSessionParameters {
-            desired: Capabilities::new(),
-            required: Capabilities::new(),
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_spec_legacy_new_session_parameters_required_only() {
-        let json = r#"{"required":{}}"#;
-        let data = LegacyNewSessionParameters {
-            desired: Capabilities::new(),
-            required: Capabilities::new(),
+        let required = if let Some(capabilities) = data.get("requiredCapabilities") {
+            try_opt!(
+                capabilities.as_object(),
+                ErrorStatus::InvalidArgument,
+                "Malformed legacy capabilities, requiredCapabilities field is not an object"
+            ).clone()
+        } else {
+            BTreeMap::new()
         };
 
-        check_deserialize(&json, &data);
+        Ok(LegacyNewSessionParameters { desired, required })
     }
-
-    #[test]
-    fn test_json_spec_legacy_new_session_parameters_desired_null() {
-        let json = r#"{"desired":null,"required":{}}"#;
-
-        assert!(serde_json::from_str::<LegacyNewSessionParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_spec_legacy_new_session_parameters_required_null() {
-        let json = r#"{"desired":{}, "required":null}"#;
-
-        assert!(serde_json::from_str::<LegacyNewSessionParameters>(&json).is_err());
-    }
+}
 
-    #[test]
-    fn test_json_spec_legacy_new_session_parameters_both_empty() {
-        let json = r#"{"desired":{},"required":{}}"#;
-        let data = LegacyNewSessionParameters {
-            desired: Capabilities::new(),
-            required: Capabilities::new(),
-        };
-
-        check_deserialize(&json, &data);
+impl ToJson for LegacyNewSessionParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("desiredCapabilities".to_owned(), self.desired.to_json());
+        data.insert("requiredCapabilities".to_owned(), self.required.to_json());
+        Json::Object(data)
     }
+}
 
-    #[test]
-    fn test_json_spec_legacy_new_session_parameters_both_with_capabilities() {
-        let json = r#"{"desired":{"foo":"bar"},"required":{"foo2":"bar2"}}"#;
-        let mut data = LegacyNewSessionParameters {
-            desired: Capabilities::new(),
-            required: Capabilities::new(),
-        };
-        data.desired.insert("foo".into(), "bar".into());
-        data.required.insert("foo2".into(), "bar2".into());
+#[cfg(test)]
+mod tests {
+    use rustc_serialize::json::Json;
+    use super::{SpecNewSessionParameters, WebDriverResult};
 
-        check_deserialize(&json, &data);
+    fn validate_proxy(value: &str) -> WebDriverResult<()> {
+        let data = Json::from_str(value).unwrap();
+        SpecNewSessionParameters::validate_proxy(&data)
     }
 
     #[test]
     fn test_validate_proxy() {
         // proxy hosts
         validate_proxy("{\"httpProxy\": \"127.0.0.1\"}").unwrap();
         validate_proxy("{\"httpProxy\": \"127.0.0.1:\"}").unwrap();
         validate_proxy("{\"httpProxy\": \"127.0.0.1:3128\"}").unwrap();
--- a/testing/webdriver/src/command.rs
+++ b/testing/webdriver/src/command.rs
@@ -1,17 +1,18 @@
-use actions::ActionSequence;
-use capabilities::{BrowserCapabilities, Capabilities, CapabilitiesMatching,
-                   LegacyNewSessionParameters, SpecNewSessionParameters};
-use common::{Date, FrameId, LocatorStrategy, WebElement};
-use error::{ErrorStatus, WebDriverError, WebDriverResult};
-use httpapi::{Route, VoidWebDriverExtensionRoute, WebDriverExtensionRoute};
+use actions::{ActionSequence};
+use capabilities::{SpecNewSessionParameters, LegacyNewSessionParameters,
+                   CapabilitiesMatching, BrowserCapabilities, Capabilities};
+use common::{Date, Nullable, WebElement, FrameId, LocatorStrategy};
+use error::{WebDriverResult, WebDriverError, ErrorStatus};
+use httpapi::{Route, WebDriverExtensionRoute, VoidWebDriverExtensionRoute};
 use regex::Captures;
-use serde::de::{self, Deserialize, Deserializer};
-use serde_json::{self, Value};
+use rustc_serialize::json;
+use rustc_serialize::json::{ToJson, Json};
+use std::collections::BTreeMap;
 
 #[derive(Debug, PartialEq)]
 pub enum WebDriverCommand<T: WebDriverExtensionCommand> {
     NewSession(NewSessionParameters),
     DeleteSession,
     Get(GetParameters),
     GetCurrentUrl,
     GoBack,
@@ -59,1020 +60,1078 @@ pub enum WebDriverCommand<T: WebDriverEx
     ElementSendKeys(WebElement, SendKeysParameters),
     PerformActions(ActionsParameters),
     ReleaseActions,
     DismissAlert,
     AcceptAlert,
     GetAlertText,
     SendAlertText(SendKeysParameters),
     TakeScreenshot,
-    TakeElementScreenshot(TakeScreenshotParameters),
+    TakeElementScreenshot(WebElement),
     Status,
-    Extension(T),
+    Extension(T)
 }
 
-pub trait WebDriverExtensionCommand: Clone + Send + PartialEq {
-    fn parameters_json(&self) -> Option<Value>;
+pub trait WebDriverExtensionCommand : Clone + Send + PartialEq {
+    fn parameters_json(&self) -> Option<Json>;
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub struct VoidWebDriverExtensionCommand;
 
 impl WebDriverExtensionCommand for VoidWebDriverExtensionCommand {
-    fn parameters_json(&self) -> Option<Value> {
+    fn parameters_json(&self) -> Option<Json> {
         panic!("No extensions implemented");
     }
 }
 
 #[derive(Debug, PartialEq)]
-pub struct WebDriverMessage<U: WebDriverExtensionRoute = VoidWebDriverExtensionRoute> {
+pub struct WebDriverMessage <U: WebDriverExtensionRoute=VoidWebDriverExtensionRoute> {
     pub session_id: Option<String>,
     pub command: WebDriverCommand<U::Command>,
 }
 
 impl<U: WebDriverExtensionRoute> WebDriverMessage<U> {
-    pub fn new(
-        session_id: Option<String>,
-        command: WebDriverCommand<U::Command>,
-    ) -> WebDriverMessage<U> {
+    pub fn new(session_id: Option<String>,
+               command: WebDriverCommand<U::Command>)
+               -> WebDriverMessage<U> {
         WebDriverMessage {
             session_id: session_id,
             command: command,
         }
     }
 
-    pub fn from_http(
-        match_type: Route<U>,
-        params: &Captures,
-        raw_body: &str,
-        requires_body: bool,
-    ) -> WebDriverResult<WebDriverMessage<U>> {
+    pub fn from_http(match_type: Route<U>,
+                     params: &Captures,
+                     raw_body: &str,
+                     requires_body: bool)
+                     -> WebDriverResult<WebDriverMessage<U>> {
         let session_id = WebDriverMessage::<U>::get_session_id(params);
-        let body_data = WebDriverMessage::<U>::decode_body(raw_body, requires_body)?;
+        let body_data = try!(WebDriverMessage::<U>::decode_body(raw_body, requires_body));
+
         let command = match match_type {
-            Route::NewSession => WebDriverCommand::NewSession(serde_json::from_str(raw_body)?),
+            Route::NewSession => {
+                let parameters: NewSessionParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::NewSession(parameters)
+            },
             Route::DeleteSession => WebDriverCommand::DeleteSession,
-            Route::Get => WebDriverCommand::Get(serde_json::from_str(raw_body)?),
+            Route::Get => {
+                let parameters: GetParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::Get(parameters)
+            },
             Route::GetCurrentUrl => WebDriverCommand::GetCurrentUrl,
             Route::GoBack => WebDriverCommand::GoBack,
             Route::GoForward => WebDriverCommand::GoForward,
             Route::Refresh => WebDriverCommand::Refresh,
             Route::GetTitle => WebDriverCommand::GetTitle,
             Route::GetPageSource => WebDriverCommand::GetPageSource,
             Route::GetWindowHandle => WebDriverCommand::GetWindowHandle,
             Route::GetWindowHandles => WebDriverCommand::GetWindowHandles,
             Route::CloseWindow => WebDriverCommand::CloseWindow,
             Route::GetTimeouts => WebDriverCommand::GetTimeouts,
-            Route::SetTimeouts => WebDriverCommand::SetTimeouts(serde_json::from_str(raw_body)?),
-            Route::GetWindowRect | Route::GetWindowPosition | Route::GetWindowSize => {
-                WebDriverCommand::GetWindowRect
-            }
+            Route::SetTimeouts => {
+                let parameters: TimeoutsParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::SetTimeouts(parameters)
+            },
+            Route::GetWindowRect | Route::GetWindowPosition | Route::GetWindowSize => WebDriverCommand::GetWindowRect,
             Route::SetWindowRect | Route::SetWindowPosition | Route::SetWindowSize => {
-                WebDriverCommand::SetWindowRect(serde_json::from_str(raw_body)?)
-            }
+                let parameters: WindowRectParameters = Parameters::from_json(&body_data)?;
+                WebDriverCommand::SetWindowRect(parameters)
+            },
             Route::MinimizeWindow => WebDriverCommand::MinimizeWindow,
             Route::MaximizeWindow => WebDriverCommand::MaximizeWindow,
             Route::FullscreenWindow => WebDriverCommand::FullscreenWindow,
             Route::SwitchToWindow => {
-                WebDriverCommand::SwitchToWindow(serde_json::from_str(raw_body)?)
+                let parameters: SwitchToWindowParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::SwitchToWindow(parameters)
             }
             Route::SwitchToFrame => {
-                WebDriverCommand::SwitchToFrame(serde_json::from_str(raw_body)?)
-            }
+                let parameters: SwitchToFrameParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::SwitchToFrame(parameters)
+            },
             Route::SwitchToParentFrame => WebDriverCommand::SwitchToParentFrame,
-            Route::FindElement => WebDriverCommand::FindElement(serde_json::from_str(raw_body)?),
-            Route::FindElements => WebDriverCommand::FindElements(serde_json::from_str(raw_body)?),
+            Route::FindElement => {
+                let parameters: LocatorParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::FindElement(parameters)
+            },
+            Route::FindElements => {
+                let parameters: LocatorParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::FindElements(parameters)
+            },
             Route::FindElementElement => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
-                WebDriverCommand::FindElementElement(element, serde_json::from_str(raw_body)?)
-            }
+                let parameters: LocatorParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::FindElementElement(element, parameters)
+            },
             Route::FindElementElements => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
-                WebDriverCommand::FindElementElements(element, serde_json::from_str(raw_body)?)
-            }
+                let parameters: LocatorParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::FindElementElements(element, parameters)
+            },
             Route::GetActiveElement => WebDriverCommand::GetActiveElement,
             Route::IsDisplayed => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::IsDisplayed(element)
-            }
+            },
             Route::IsSelected => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::IsSelected(element)
-            }
+            },
             Route::GetElementAttribute => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
-                let attr = try_opt!(
-                    params.name("name"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing name parameter"
-                ).as_str();
+                let attr = try_opt!(params.name("name"),
+                                    ErrorStatus::InvalidArgument,
+                                    "Missing name parameter").as_str();
                 WebDriverCommand::GetElementAttribute(element, attr.into())
-            }
+            },
             Route::GetElementProperty => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
-                let property = try_opt!(
-                    params.name("name"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing name parameter"
-                ).as_str();
+                let property = try_opt!(params.name("name"),
+                                        ErrorStatus::InvalidArgument,
+                                        "Missing name parameter").as_str();
                 WebDriverCommand::GetElementProperty(element, property.into())
-            }
+            },
             Route::GetCSSValue => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
-                let property = try_opt!(
-                    params.name("propertyName"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing propertyName parameter"
-                ).as_str();
+                let property = try_opt!(params.name("propertyName"),
+                                        ErrorStatus::InvalidArgument,
+                                        "Missing propertyName parameter").as_str();
                 WebDriverCommand::GetCSSValue(element, property.into())
-            }
+            },
             Route::GetElementText => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::GetElementText(element)
-            }
+            },
             Route::GetElementTagName => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::GetElementTagName(element)
-            }
+            },
             Route::GetElementRect => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::GetElementRect(element)
-            }
+            },
             Route::IsEnabled => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::IsEnabled(element)
-            }
+            },
             Route::ElementClick => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::ElementClick(element)
-            }
+            },
             Route::ElementTap => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::ElementTap(element)
-            }
+            },
             Route::ElementClear => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::ElementClear(element)
-            }
+            },
             Route::ElementSendKeys => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
-                WebDriverCommand::ElementSendKeys(element, serde_json::from_str(raw_body)?)
-            }
+                let parameters: SendKeysParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::ElementSendKeys(element, parameters)
+            },
             Route::ExecuteScript => {
-                WebDriverCommand::ExecuteScript(serde_json::from_str(raw_body)?)
-            }
+                let parameters: JavascriptCommandParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::ExecuteScript(parameters)
+            },
             Route::ExecuteAsyncScript => {
-                WebDriverCommand::ExecuteAsyncScript(serde_json::from_str(raw_body)?)
-            }
-            Route::GetCookies => WebDriverCommand::GetCookies,
+                let parameters: JavascriptCommandParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::ExecuteAsyncScript(parameters)
+            },
+            Route::GetCookies => {
+                WebDriverCommand::GetCookies
+            },
             Route::GetNamedCookie => {
-                let name = try_opt!(
-                    params.name("name"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing 'name' parameter"
-                ).as_str()
-                    .into();
+                let name = try_opt!(params.name("name"),
+                                    ErrorStatus::InvalidArgument,
+                                    "Missing 'name' parameter").as_str().into();
                 WebDriverCommand::GetNamedCookie(name)
+            },
+            Route::AddCookie => {
+                let parameters: AddCookieParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::AddCookie(parameters)
+            },
+            Route::DeleteCookies => {
+                WebDriverCommand::DeleteCookies
+            },
+            Route::DeleteCookie => {
+                let name = try_opt!(params.name("name"),
+                                    ErrorStatus::InvalidArgument,
+                                    "Missing name parameter").as_str().into();
+                WebDriverCommand::DeleteCookie(name)
+            },
+            Route::PerformActions => {
+                let parameters: ActionsParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::PerformActions(parameters)
+            },
+            Route::ReleaseActions => {
+                WebDriverCommand::ReleaseActions
+            },
+            Route::DismissAlert => {
+                WebDriverCommand::DismissAlert
+            },
+            Route::AcceptAlert => {
+                WebDriverCommand::AcceptAlert
+            },
+            Route::GetAlertText => {
+                WebDriverCommand::GetAlertText
+            },
+            Route::SendAlertText => {
+                let parameters: SendKeysParameters = try!(Parameters::from_json(&body_data));
+                WebDriverCommand::SendAlertText(parameters)
+            },
+            Route::TakeScreenshot => WebDriverCommand::TakeScreenshot,
+            Route::TakeElementScreenshot =>  {
+                let element_id = try_opt!(params.name("elementId"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing elementId parameter");
+                let element = WebElement::new(element_id.as_str().into());
+                WebDriverCommand::TakeElementScreenshot(element)
+            },
+            Route::Status => WebDriverCommand::Status,
+            Route::Extension(ref extension) => {
+                try!(extension.command(params, &body_data))
             }
-            Route::AddCookie => WebDriverCommand::AddCookie(serde_json::from_str(raw_body)?),
-            Route::DeleteCookies => WebDriverCommand::DeleteCookies,
-            Route::DeleteCookie => {
-                let name = try_opt!(
-                    params.name("name"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing name parameter"
-                ).as_str()
-                    .into();
-                WebDriverCommand::DeleteCookie(name)
-            }
-            Route::PerformActions => {
-                WebDriverCommand::PerformActions(serde_json::from_str(raw_body)?)
-            }
-            Route::ReleaseActions => WebDriverCommand::ReleaseActions,
-            Route::DismissAlert => WebDriverCommand::DismissAlert,
-            Route::AcceptAlert => WebDriverCommand::AcceptAlert,
-            Route::GetAlertText => WebDriverCommand::GetAlertText,
-            Route::SendAlertText => {
-                WebDriverCommand::SendAlertText(serde_json::from_str(raw_body)?)
-            }
-            Route::TakeScreenshot => WebDriverCommand::TakeScreenshot,
-            Route::TakeElementScreenshot => {
-                WebDriverCommand::TakeElementScreenshot(serde_json::from_str(raw_body)?)
-            }
-            Route::Status => WebDriverCommand::Status,
-            Route::Extension(ref extension) => try!(extension.command(params, &body_data)),
         };
         Ok(WebDriverMessage::new(session_id, command))
     }
 
     fn get_session_id(params: &Captures) -> Option<String> {
         params.name("sessionId").map(|x| x.as_str().into())
     }
 
-    fn decode_body(body: &str, requires_body: bool) -> WebDriverResult<Value> {
+    fn decode_body(body: &str, requires_body: bool) -> WebDriverResult<Json> {
         if requires_body {
-            match serde_json::from_str(body) {
-                Ok(x @ Value::Object(_)) => Ok(x),
-                Ok(_) => Err(WebDriverError::new(
-                    ErrorStatus::InvalidArgument,
-                    "Body was not a JSON Object",
-                )),
-                Err(e) => {
-                    if e.is_io() {
-                        Err(WebDriverError::new(
-                            ErrorStatus::InvalidArgument,
-                            format!("I/O error whilst decoding body: {}", e),
-                        ))
-                    } else {
-                        let msg = format!("Failed to decode request as JSON: {}", body);
-                        let stack = format!("Syntax error at :{}:{}", e.line(), e.column());
-                        Err(WebDriverError::new_with_stack(
-                            ErrorStatus::InvalidArgument,
-                            msg,
-                            stack,
-                        ))
-                    }
+            match Json::from_str(body) {
+                Ok(x @ Json::Object(_)) => Ok(x),
+                Ok(_) => {
+                    Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                            "Body was not a JSON Object"))
+                }
+                Err(json::ParserError::SyntaxError(_, line, col)) => {
+                    let msg = format!("Failed to decode request as JSON: \"{}\"", body);
+                    let stack = format!("Syntax error at :{}:{}", line, col);
+                    Err(WebDriverError::new_with_stack(ErrorStatus::InvalidArgument, msg, stack))
+                }
+                Err(json::ParserError::IoError(e)) => {
+                    Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                            format!("I/O error whilst decoding body: {}", e)))
                 }
             }
         } else {
-            Ok(Value::Null)
+            Ok(Json::Null)
         }
     }
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct ActionsParameters {
-    pub actions: Vec<ActionSequence>,
-}
+impl <U:WebDriverExtensionRoute> ToJson for WebDriverMessage<U> {
+    fn to_json(&self) -> Json {
+        let parameters = match self.command {
+            WebDriverCommand::AcceptAlert |
+            WebDriverCommand::CloseWindow |
+            WebDriverCommand::ReleaseActions |
+            WebDriverCommand::DeleteCookie(_) |
+            WebDriverCommand::DeleteCookies |
+            WebDriverCommand::DeleteSession |
+            WebDriverCommand::DismissAlert |
+            WebDriverCommand::ElementClear(_) |
+            WebDriverCommand::ElementClick(_) |
+            WebDriverCommand::ElementTap(_) |
+            WebDriverCommand::GetActiveElement |
+            WebDriverCommand::GetAlertText |
+            WebDriverCommand::GetNamedCookie(_) |
+            WebDriverCommand::GetCookies |
+            WebDriverCommand::GetCSSValue(_, _) |
+            WebDriverCommand::GetCurrentUrl |
+            WebDriverCommand::GetElementAttribute(_, _) |
+            WebDriverCommand::GetElementProperty(_, _) |
+            WebDriverCommand::GetElementRect(_) |
+            WebDriverCommand::GetElementTagName(_) |
+            WebDriverCommand::GetElementText(_) |
+            WebDriverCommand::GetPageSource |
+            WebDriverCommand::GetTimeouts |
+            WebDriverCommand::GetTitle |
+            WebDriverCommand::GetWindowHandle |
+            WebDriverCommand::GetWindowHandles |
+            WebDriverCommand::GetWindowRect |
+            WebDriverCommand::GoBack |
+            WebDriverCommand::GoForward |
+            WebDriverCommand::IsDisplayed(_) |
+            WebDriverCommand::IsEnabled(_) |
+            WebDriverCommand::IsSelected(_) |
+            WebDriverCommand::MinimizeWindow |
+            WebDriverCommand::MaximizeWindow |
+            WebDriverCommand::FullscreenWindow |
+            WebDriverCommand::NewSession(_) |
+            WebDriverCommand::Refresh |
+            WebDriverCommand::Status |
+            WebDriverCommand::SwitchToParentFrame |
+            WebDriverCommand::TakeElementScreenshot(_) |
+            WebDriverCommand::TakeScreenshot => {
+                None
+            },
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-#[serde(remote = "Self")]
-pub struct AddCookieParameters {
-    pub name: String,
-    pub value: String,
-    pub path: Option<String>,
-    pub domain: Option<String>,
-    #[serde(default)]
-    pub secure: bool,
-    #[serde(default)]
-    pub httpOnly: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub expiry: Option<Date>,
-}
+            WebDriverCommand::AddCookie(ref x) => Some(x.to_json()),
+            WebDriverCommand::ElementSendKeys(_, ref x) => Some(x.to_json()),
+            WebDriverCommand::ExecuteAsyncScript(ref x) |
+            WebDriverCommand::ExecuteScript(ref x) => Some(x.to_json()),
+            WebDriverCommand::FindElementElement(_, ref x) => Some(x.to_json()),
+            WebDriverCommand::FindElementElements(_, ref x) => Some(x.to_json()),
+            WebDriverCommand::FindElement(ref x) => Some(x.to_json()),
+            WebDriverCommand::FindElements(ref x) => Some(x.to_json()),
+            WebDriverCommand::Get(ref x) => Some(x.to_json()),
+            WebDriverCommand::PerformActions(ref x) => Some(x.to_json()),
+            WebDriverCommand::SendAlertText(ref x) => Some(x.to_json()),
+            WebDriverCommand::SetTimeouts(ref x) => Some(x.to_json()),
+            WebDriverCommand::SetWindowRect(ref x) => Some(x.to_json()),
+            WebDriverCommand::SwitchToFrame(ref x) => Some(x.to_json()),
+            WebDriverCommand::SwitchToWindow(ref x) => Some(x.to_json()),
+            WebDriverCommand::Extension(ref x) => x.parameters_json(),
+        };
 
-impl<'de> Deserialize<'de> for AddCookieParameters {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        #[derive(Deserialize)]
-        struct Wrapper {
-            #[serde(with = "AddCookieParameters")]
-            cookie: AddCookieParameters,
+        let mut data = BTreeMap::new();
+        if let Some(parameters) = parameters {
+            data.insert("parameters".to_string(), parameters);
         }
-
-        Wrapper::deserialize(deserializer).map(|wrapper| wrapper.cookie)
+        Json::Object(data)
     }
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct GetParameters {
-    pub url: String,
-}
-
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct GetNamedCookieParameters {
-    pub name: Option<String>,
-}
-
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct JavascriptCommandParameters {
-    pub script: String,
-    pub args: Option<Vec<Value>>,
-}
-
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct LocatorParameters {
-    pub using: LocatorStrategy,
-    pub value: String,
+pub trait Parameters: Sized {
+    fn from_json(body: &Json) -> WebDriverResult<Self>;
 }
 
 /// Wrapper around the two supported variants of new session paramters
 ///
 /// The Spec variant is used for storing spec-compliant parameters whereas
 /// the legacy variant is used to store desiredCapabilities/requiredCapabilities
 /// parameters, and is intended to minimise breakage as we transition users to
 /// the spec design.
-
 #[derive(Debug, PartialEq)]
 pub enum NewSessionParameters {
     Spec(SpecNewSessionParameters),
     Legacy(LegacyNewSessionParameters),
 }
 
-impl<'de> Deserialize<'de> for NewSessionParameters {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        let value = serde_json::Value::deserialize(deserializer)?;
-        if let Some(caps) = value.get("capabilities") {
-            let caps = SpecNewSessionParameters::deserialize(caps).map_err(de::Error::custom)?;
-            return Ok(NewSessionParameters::Spec(caps));
+impl Parameters for NewSessionParameters {
+    fn from_json(body: &Json) -> WebDriverResult<NewSessionParameters> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::UnknownError,
+                            "Message body was not an object");
+        if data.get("capabilities").is_some() {
+            Ok(NewSessionParameters::Spec(try!(SpecNewSessionParameters::from_json(body))))
+        } else {
+            Ok(NewSessionParameters::Legacy(try!(LegacyNewSessionParameters::from_json(body))))
         }
+    }
+}
 
-        let legacy = LegacyNewSessionParameters::deserialize(value).map_err(de::Error::custom)?;
-        Ok(NewSessionParameters::Legacy(legacy))
+impl ToJson for NewSessionParameters {
+    fn to_json(&self) -> Json {
+        match self {
+            &NewSessionParameters::Spec(ref x) => x.to_json(),
+            &NewSessionParameters::Legacy(ref x) => x.to_json()
+        }
     }
 }
 
 impl CapabilitiesMatching for NewSessionParameters {
-    fn match_browser<T: BrowserCapabilities>(
-        &self,
-        browser_capabilities: &mut T,
-    ) -> WebDriverResult<Option<Capabilities>> {
+    fn match_browser<T: BrowserCapabilities>(&self, browser_capabilities: &mut T)
+                                             -> WebDriverResult<Option<Capabilities>> {
         match self {
             &NewSessionParameters::Spec(ref x) => x.match_browser(browser_capabilities),
-            &NewSessionParameters::Legacy(ref x) => x.match_browser(browser_capabilities),
+            &NewSessionParameters::Legacy(ref x) => x.match_browser(browser_capabilities)
         }
     }
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct SendKeysParameters {
-    pub text: String,
+
+#[derive(Debug, PartialEq)]
+pub struct GetParameters {
+    pub url: String
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct SwitchToFrameParameters {
-    pub id: Option<FrameId>,
+impl Parameters for GetParameters {
+    fn from_json(body: &Json) -> WebDriverResult<GetParameters> {
+        let data = try_opt!(body.as_object(), ErrorStatus::UnknownError,
+                            "Message body was not an object");
+        let url = try_opt!(
+            try_opt!(data.get("url"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'url' parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "'url' not a string");
+        Ok(GetParameters {
+            url: url.to_string()
+        })
+    }
+}
+
+impl ToJson for GetParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("url".to_string(), self.url.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct TimeoutsParameters {
+    pub script: Option<u64>,
+    pub page_load: Option<u64>,
+    pub implicit: Option<u64>,
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct SwitchToWindowParameters {
-    pub handle: String,
+impl Parameters for TimeoutsParameters {
+    fn from_json(body: &Json) -> WebDriverResult<TimeoutsParameters> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::UnknownError,
+                            "Message body was not an object");
+
+        let script = match data.get("script") {
+            Some(json) => {
+                Some(try_opt!(json.as_u64(),
+                              ErrorStatus::InvalidArgument,
+                              "Script timeout duration was not a signed integer"))
+            }
+            None => None,
+        };
+
+        let page_load = match data.get("pageLoad") {
+            Some(json) => {
+                Some(try_opt!(json.as_u64(),
+                              ErrorStatus::InvalidArgument,
+                              "Page load timeout duration was not a signed integer"))
+            }
+            None => None,
+        };
+
+        let implicit = match data.get("implicit") {
+            Some(json) => {
+                Some(try_opt!(json.as_u64(),
+                              ErrorStatus::InvalidArgument,
+                              "Implicit timeout duration was not a signed integer"))
+            }
+            None => None,
+        };
+
+        Ok(TimeoutsParameters {
+            script: script,
+            page_load: page_load,
+            implicit: implicit,
+        })
+    }
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct TakeScreenshotParameters {
-    pub element: Option<WebElement>,
-}
-
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct TimeoutsParameters {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub implicit: Option<u64>,
-    #[serde(rename = "pageLoad", skip_serializing_if = "Option::is_none")]
-    pub page_load: Option<u64>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub script: Option<u64>,
+impl ToJson for TimeoutsParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        if let Some(ms) = self.script {
+            data.insert("script".into(), ms.to_json());
+        }
+        if let Some(ms) = self.page_load {
+            data.insert("pageLoad".into(), ms.to_json());
+        }
+        if let Some(ms) = self.implicit {
+            data.insert("implicit".into(), ms.to_json());
+        }
+        Json::Object(data)
+    }
 }
 
 /// A top-level browsing context’s window rect is a dictionary of the
 /// [`screenX`], [`screenY`], `width`, and `height` attributes of the
 /// `WindowProxy`.
 ///
 /// In some user agents the operating system’s window dimensions, including
 /// decorations, are provided by the proprietary `window.outerWidth` and
 /// `window.outerHeight` DOM properties.
 ///
 /// [`screenX`]: https://w3c.github.io/webdriver/webdriver-spec.html#dfn-screenx
 /// [`screenY`]: https://w3c.github.io/webdriver/webdriver-spec.html#dfn-screeny
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq)]
 pub struct WindowRectParameters {
-    #[serde(default, deserialize_with = "deserialize_to_i32")]
-    pub x: Option<i32>,
-    #[serde(default, deserialize_with = "deserialize_to_i32")]
-    pub y: Option<i32>,
-    #[serde(default, deserialize_with = "deserialize_to_positive_i32")]
-    pub width: Option<i32>,
-    #[serde(default, deserialize_with = "deserialize_to_positive_i32")]
-    pub height: Option<i32>,
+    pub x: Nullable<i32>,
+    pub y: Nullable<i32>,
+    pub width: Nullable<i32>,
+    pub height: Nullable<i32>,
+}
+
+impl Parameters for WindowRectParameters {
+    fn from_json(body: &Json) -> WebDriverResult<WindowRectParameters> {
+        let data = try_opt!(body.as_object(),
+            ErrorStatus::InvalidArgument, "Message body was not an object");
+
+        let x = match data.get("x") {
+            Some(json) => try!(Nullable::from_json(json, |n| {
+                let x = try_opt!(
+                    n.as_f64(),
+                    ErrorStatus::InvalidArgument,
+                    "'x' is not a number"
+                ) as i64;
+                if x < i32::min_value() as i64 || x > i32::max_value() as i64 {
+                    return Err(WebDriverError::new(
+                        ErrorStatus::InvalidArgument,
+                        "'x' is larger than i32",
+                    ));
+                }
+                Ok(x as i32)
+            })),
+            None => Nullable::Null,
+        };
+
+        let y = match data.get("y") {
+            Some(json) => try!(Nullable::from_json(json, |n| {
+                let y = try_opt!(
+                    n.as_f64(),
+                    ErrorStatus::InvalidArgument,
+                    "'y' is not a number"
+                ) as i64;
+                if y < i32::min_value() as i64 || y > i32::max_value() as i64 {
+                    return Err(WebDriverError::new(
+                        ErrorStatus::InvalidArgument,
+                        "'y' is larger than i32",
+                    ));
+                }
+                Ok(y as i32)
+            })),
+            None => Nullable::Null,
+        };
+
+        let width = match data.get("width") {
+            Some(json) => try!(Nullable::from_json(json, |n| {
+                let width = try_opt!(
+                    n.as_f64(),
+                    ErrorStatus::InvalidArgument,
+                    "'width' is not a number"
+                ) as i64;
+                if width < 0 || width > i32::max_value() as i64 {
+                    return Err(WebDriverError::new(
+                        ErrorStatus::InvalidArgument,
+                        "'width' is larger than i32",
+                    ));
+                }
+                Ok(width as i32)
+            })),
+            None => Nullable::Null,
+        };
+
+        let height = match data.get("height") {
+            Some(json) => try!(Nullable::from_json(json, |n| {
+                let height = try_opt!(
+                    n.as_f64(),
+                    ErrorStatus::InvalidArgument,
+                    "'height' is not a positive integer"
+                ) as i64;
+                if height < 0 || height > i32::max_value() as i64 {
+                    return Err(WebDriverError::new(
+                        ErrorStatus::InvalidArgument,
+                        "'height' is larger than i32",
+                    ));
+                }
+                Ok(height as i32)
+            })),
+            None => Nullable::Null,
+        };
+
+        Ok(WindowRectParameters { x, y, width, height })
+    }
+}
+
+impl ToJson for WindowRectParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("x".to_string(), self.x.to_json());
+        data.insert("y".to_string(), self.y.to_json());
+        data.insert("width".to_string(), self.width.to_json());
+        data.insert("height".to_string(), self.height.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct SwitchToWindowParameters {
+    pub handle: String
+}
+
+impl Parameters for SwitchToWindowParameters {
+    fn from_json(body: &Json) -> WebDriverResult<SwitchToWindowParameters> {
+        let data = try_opt!(body.as_object(), ErrorStatus::UnknownError,
+                            "Message body was not an object");
+        let handle = try_opt!(
+            try_opt!(data.get("handle"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'handle' parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "'handle' not a string");
+        return Ok(SwitchToWindowParameters {
+            handle: handle.to_string()
+        })
+    }
+}
+
+impl ToJson for SwitchToWindowParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("handle".to_string(), self.handle.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct LocatorParameters {
+    pub using: LocatorStrategy,
+    pub value: String
+}
+
+impl Parameters for LocatorParameters {
+    fn from_json(body: &Json) -> WebDriverResult<LocatorParameters> {
+        let data = try_opt!(body.as_object(), ErrorStatus::UnknownError,
+                            "Message body was not an object");
+
+        let using = try!(LocatorStrategy::from_json(
+            try_opt!(data.get("using"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'using' parameter")));
+
+        let value = try_opt!(
+            try_opt!(data.get("value"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'value' parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "Could not convert using to string").to_string();
+
+        return Ok(LocatorParameters {
+            using: using,
+            value: value
+        })
+    }
+}
+
+impl ToJson for LocatorParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("using".to_string(), self.using.to_json());
+        data.insert("value".to_string(), self.value.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct SwitchToFrameParameters {
+    pub id: FrameId
+}
+
+impl Parameters for SwitchToFrameParameters {
+    fn from_json(body: &Json) -> WebDriverResult<SwitchToFrameParameters> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::UnknownError,
+                            "Message body was not an object");
+        let id = try!(FrameId::from_json(try_opt!(data.get("id"),
+                                                  ErrorStatus::UnknownError,
+                                                  "Missing 'id' parameter")));
+
+        Ok(SwitchToFrameParameters {
+            id: id
+        })
+    }
+}
+
+impl ToJson for SwitchToFrameParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("id".to_string(), self.id.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct SendKeysParameters {
+    pub text: String
+}
+
+impl Parameters for SendKeysParameters {
+    fn from_json(body: &Json) -> WebDriverResult<SendKeysParameters> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Message body was not an object");
+        let text = try_opt!(try_opt!(data.get("text"),
+                                     ErrorStatus::InvalidArgument,
+                                     "Missing 'text' parameter").as_string(),
+                            ErrorStatus::InvalidArgument,
+                            "Could not convert 'text' to string");
+
+        Ok(SendKeysParameters {
+            text: text.into()
+        })
+    }
+}
+
+impl ToJson for SendKeysParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("value".to_string(), self.text.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct JavascriptCommandParameters {
+    pub script: String,
+    pub args: Nullable<Vec<Json>>
 }
 
-fn deserialize_to_i32<'de, D>(deserializer: D) -> Result<Option<i32>, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    let opt = Option::deserialize(deserializer)?.map(|value: f64| value as i64);
-    let value = match opt {
-        Some(n) => {
-            if n < i32::min_value() as i64 || n > i32::max_value() as i64 {
-                return Err(de::Error::custom(format!("'{}' is larger than i32", n)));
-            }
-            Some(n as i32)
-        }
-        None => None,
-    };
+impl Parameters for JavascriptCommandParameters {
+    fn from_json(body: &Json) -> WebDriverResult<JavascriptCommandParameters> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Message body was not an object");
+
+        let args_json = try_opt!(data.get("args"),
+                                 ErrorStatus::InvalidArgument,
+                                 "Missing args parameter");
+
+        let args = try!(Nullable::from_json(
+            args_json,
+            |x| {
+                Ok((try_opt!(x.as_array(),
+                             ErrorStatus::InvalidArgument,
+                             "Failed to convert args to Array")).clone())
+            }));
+
+         //TODO: Look for WebElements in args?
+        let script = try_opt!(
+            try_opt!(data.get("script"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing script parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "Failed to convert script to String");
+        Ok(JavascriptCommandParameters {
+            script: script.to_string(),
+            args: args.clone()
+        })
+    }
+}
 
-    Ok(value)
+impl ToJson for JavascriptCommandParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        //TODO: Wrap script so that it becomes marionette-compatible
+        data.insert("script".to_string(), self.script.to_json());
+        data.insert("args".to_string(), self.args.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct GetNamedCookieParameters {
+    pub name: Nullable<String>,
+}
+
+impl Parameters for GetNamedCookieParameters {
+    fn from_json(body: &Json) -> WebDriverResult<GetNamedCookieParameters> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Message body was not an object");
+        let name_json = try_opt!(data.get("name"),
+                                 ErrorStatus::InvalidArgument,
+                                 "Missing 'name' parameter");
+        let name = try!(Nullable::from_json(name_json, |x| {
+            Ok(try_opt!(x.as_string(),
+                        ErrorStatus::InvalidArgument,
+                        "Failed to convert name to string")
+                .to_string())
+        }));
+        return Ok(GetNamedCookieParameters { name: name });
+    }
+}
+
+impl ToJson for GetNamedCookieParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("name".to_string(), self.name.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct AddCookieParameters {
+    pub name: String,
+    pub value: String,
+    pub path: Nullable<String>,
+    pub domain: Nullable<String>,
+    pub expiry: Nullable<Date>,
+    pub secure: bool,
+    pub httpOnly: bool
 }
 
-fn deserialize_to_positive_i32<'de, D>(deserializer: D) -> Result<Option<i32>, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    let opt = Option::deserialize(deserializer)?.map(|value: f64| value as i64);
-    let value = match opt {
-        Some(n) => {
-            if n < 0 || n > i32::max_value() as i64 {
-                return Err(de::Error::custom(format!("'{}' is outside of i32", n)));
-            }
-            Some(n as i32)
+impl Parameters for AddCookieParameters {
+    fn from_json(body: &Json) -> WebDriverResult<AddCookieParameters> {
+        if !body.is_object() {
+            return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                           "Message body was not an object"));
         }
-        None => None,
-    };
+
+        let data = try_opt!(body.find("cookie").and_then(|x| x.as_object()),
+                            ErrorStatus::InvalidArgument,
+                            "Cookie parameter not found or not an object");
+
+        let name = try_opt!(
+            try_opt!(data.get("name"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'name' parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "'name' is not a string").to_string();
+
+        let value = try_opt!(
+            try_opt!(data.get("value"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'value' parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "'value' is not a string").to_string();
+
+        let path = match data.get("path") {
+            Some(path_json) => {
+                try!(Nullable::from_json(
+                    path_json,
+                    |x| {
+                        Ok(try_opt!(x.as_string(),
+                                    ErrorStatus::InvalidArgument,
+                                    "Failed to convert path to String").to_string())
+                    }))
+            },
+            None => Nullable::Null
+        };
+
+        let domain = match data.get("domain") {
+            Some(domain_json) => {
+                try!(Nullable::from_json(
+                    domain_json,
+                    |x| {
+                        Ok(try_opt!(x.as_string(),
+                                    ErrorStatus::InvalidArgument,
+                                    "Failed to convert domain to String").to_string())
+                    }))
+            },
+            None => Nullable::Null
+        };
+
+        let expiry = match data.get("expiry") {
+            Some(expiry_json) => {
+                try!(Nullable::from_json(
+                    expiry_json,
+                    |x| {
+                        Ok(Date::new(try_opt!(x.as_u64(),
+                                              ErrorStatus::InvalidArgument,
+                                              "Failed to convert expiry to Date")))
+                    }))
+            },
+            None => Nullable::Null
+        };
+
+        let secure = match data.get("secure") {
+            Some(x) => try_opt!(x.as_boolean(),
+                                ErrorStatus::InvalidArgument,
+                                "Failed to convert secure to boolean"),
+            None => false
+        };
+
+        let http_only = match data.get("httpOnly") {
+            Some(x) => try_opt!(x.as_boolean(),
+                                ErrorStatus::InvalidArgument,
+                                "Failed to convert httpOnly to boolean"),
+            None => false
+        };
 
-    Ok(value)
+        return Ok(AddCookieParameters {
+            name: name,
+            value: value,
+            path: path,
+            domain: domain,
+            expiry: expiry,
+            secure: secure,
+            httpOnly: http_only
+        })
+    }
+}
+
+impl ToJson for AddCookieParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("name".to_string(), self.name.to_json());
+        data.insert("value".to_string(), self.value.to_json());
+        data.insert("path".to_string(), self.path.to_json());
+        data.insert("domain".to_string(), self.domain.to_json());
+        data.insert("expiry".to_string(), self.expiry.to_json());
+        data.insert("secure".to_string(), self.secure.to_json());
+        data.insert("httpOnly".to_string(), self.httpOnly.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct TakeScreenshotParameters {
+    pub element: Nullable<WebElement>
+}
+
+impl Parameters for TakeScreenshotParameters {
+    fn from_json(body: &Json) -> WebDriverResult<TakeScreenshotParameters> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Message body was not an object");
+        let element = match data.get("element") {
+            Some(element_json) => try!(Nullable::from_json(
+                element_json,
+                |x| {
+                    Ok(try!(WebElement::from_json(x)))
+                })),
+            None => Nullable::Null
+        };
+
+        return Ok(TakeScreenshotParameters {
+            element: element
+        })
+    }
+}
+
+impl ToJson for TakeScreenshotParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("element".to_string(), self.element.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct ActionsParameters {
+    pub actions: Vec<ActionSequence>
+}
+
+impl Parameters for ActionsParameters {
+    fn from_json(body: &Json) -> WebDriverResult<ActionsParameters> {
+        try_opt!(body.as_object(),
+                 ErrorStatus::InvalidArgument,
+                 "Message body was not an object");
+        let actions = try_opt!(
+            try_opt!(body.find("actions"),
+                     ErrorStatus::InvalidArgument,
+                     "No actions parameter found").as_array(),
+            ErrorStatus::InvalidArgument,
+            "Parameter 'actions' was not an array");
+
+        let mut result = Vec::with_capacity(actions.len());
+        for chain in actions.iter() {
+            result.push(try!(ActionSequence::from_json(chain)));
+        }
+        Ok(ActionsParameters {
+            actions: result
+        })
+    }
+}
+
+impl ToJson for ActionsParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("actions".to_owned(),
+                    self.actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>().to_json());
+        Json::Object(data)
+    }
 }
 
 #[cfg(test)]
 mod tests {
-    use super::*;
-    use capabilities::SpecNewSessionParameters;
-    use serde_json;
-    use test::check_deserialize;
-
-    #[test]
-    fn test_json_actions_parameters_missing_actions_field() {
-        let json = r#"{}"#;
-        assert!(serde_json::from_str::<ActionsParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_actions_parameters_invalid() {
-        let json = r#"{"actions":null}"#;
-        assert!(serde_json::from_str::<ActionsParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_action_parameters_empty_list() {
-        let json = r#"{"actions":[]}"#;
-        let data = ActionsParameters { actions: vec![] };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_add_cookie_parameters_with_values() {
-        let json = r#"{"cookie":{
-            "name":"foo",
-            "value":"bar",
-            "path":"/",
-            "domain":"foo.bar",
-            "expiry":123,
-            "secure":true,
-            "httpOnly":false
-        }}"#;
-        let data = AddCookieParameters {
-            name: "foo".into(),
-            value: "bar".into(),
-            path: Some("/".into()),
-            domain: Some("foo.bar".into()),
-            expiry: Some(Date(123)),
-            secure: true,
-            httpOnly: false,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_add_cookie_parameters_with_optional_null_fields() {
-        let json = r#"{"cookie":{
-            "name":"foo",
-            "value":"bar",
-            "path":null,
-            "domain":null,
-            "expiry":null,
-            "secure":true,
-            "httpOnly":false
-        }}"#;
-        let data = AddCookieParameters {
-            name: "foo".into(),
-            value: "bar".into(),
-            path: None,
-            domain: None,
-            expiry: None,
-            secure: true,
-            httpOnly: false,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_add_cookie_parameters_without_optional_fields() {
-        let json = r#"{"cookie":{
-            "name":"foo",
-            "value":"bar",
-            "secure":true,
-            "httpOnly":false
-        }}"#;
-        let data = AddCookieParameters {
-            name: "foo".into(),
-            value: "bar".into(),
-            path: None,
-            domain: None,
-            expiry: None,
-            secure: true,
-            httpOnly: false,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_add_cookie_parameters_with_invalid_cookie_field() {
-        let json = r#"{"name":"foo"}"#;
-
-        assert!(serde_json::from_str::<AddCookieParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_get_parameters_with_url() {
-        let json = r#"{"url":"foo.bar"}"#;
-        let data = GetParameters {
-            url: "foo.bar".into(),
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_get_parameters_with_invalid_url_value() {
-        let json = r#"{"url":3}"#;
-
-        assert!(serde_json::from_str::<GetParameters>(&json).is_err());
-    }
+    use rustc_serialize::json::Json;
+    use super::{Nullable, Parameters, WindowRectParameters};
 
     #[test]
-    fn test_json_get_parameters_with_invalid_url_field() {
-        let json = r#"{"foo":"bar"}"#;
-
-        assert!(serde_json::from_str::<GetParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_get_named_cookie_parameters_with_value() {
-        let json = r#"{"name":"foo"}"#;
-        let data = GetNamedCookieParameters {
-            name: Some("foo".into()),
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_get_named_cookie_parameters_with_optional_null_field() {
-        let json = r#"{"name":null}"#;
-        let data = GetNamedCookieParameters { name: None };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_get_named_cookie_parameters_without_optional_null_field() {
-        let json = r#"{}"#;
-        let data = GetNamedCookieParameters { name: None };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_get_named_cookie_parameters_with_invalid_name_field() {
-        let json = r#"{"name":3"#;
-
-        assert!(serde_json::from_str::<GetNamedCookieParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_javascript_command_parameters_with_values() {
-        let json = r#"{"script":"foo","args":["1",2]}"#;
-        let data = JavascriptCommandParameters {
-            script: "foo".into(),
-            args: Some(vec!["1".into(), 2.into()]),
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_javascript_command_parameters_with_optional_null_field() {
-        let json = r#"{"script":"foo","args":null}"#;
-        let data = JavascriptCommandParameters {
-            script: "foo".into(),
-            args: None,
+    fn test_window_rect() {
+        let expected = WindowRectParameters {
+            x: Nullable::Value(0i32),
+            y: Nullable::Value(1i32),
+            width: Nullable::Value(2i32),
+            height: Nullable::Value(3i32),
         };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_javascript_command_parameters_without_optional_null_field() {
-        let json = r#"{"script":"foo"}"#;
-        let data = JavascriptCommandParameters {
-            script: "foo".into(),
-            args: None,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_javascript_command_parameters_invalid_script_field() {
-        let json = r#"{"script":null}"#;
-
-        assert!(serde_json::from_str::<JavascriptCommandParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_javascript_command_parameters_invalid_args_field() {
-        let json = r#"{"script":null,"args":"1"}"#;
-
-        assert!(serde_json::from_str::<JavascriptCommandParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_javascript_command_parameters_missing_script_field() {
-        let json = r#"{"args":null}"#;
-
-        assert!(serde_json::from_str::<JavascriptCommandParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_locator_parameters_with_values() {
-        let json = r#"{"using":"xpath","value":"bar"}"#;
-        let data = LocatorParameters {
-            using: LocatorStrategy::XPath,
-            value: "bar".into(),
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_locator_parameters_invalid_using_field() {
-        let json = r#"{"using":"foo","value":"bar"}"#;
-
-        assert!(serde_json::from_str::<LocatorParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_locator_parameters_invalid_value_field() {
-        let json = r#"{"using":"xpath","value":3}"#;
-
-        assert!(serde_json::from_str::<LocatorParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_locator_parameters_missing_using_field() {
-        let json = r#"{"value":"bar"}"#;
-
-        assert!(serde_json::from_str::<LocatorParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_locator_parameters_missing_value_field() {
-        let json = r#"{"using":"xpath"}"#;
-
-        assert!(serde_json::from_str::<LocatorParameters>(&json).is_err());
+        let actual = Json::from_str(r#"{"x": 0, "y": 1, "width": 2, "height": 3}"#).unwrap();
+        assert_eq!(expected, Parameters::from_json(&actual).unwrap());
     }
 
     #[test]
-    fn test_json_new_session_parameters_spec() {
-        let json = r#"{"capabilities":{"alwaysMatch":{},"firstMatch":[{}]}}"#;
-        let data = NewSessionParameters::Spec(SpecNewSessionParameters {
-            alwaysMatch: Capabilities::new(),
-            firstMatch: vec![Capabilities::new()],
-        });
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_new_session_parameters_capabilities_null() {
-        let json = r#"{"capabilities":null}"#;
-
-        assert!(serde_json::from_str::<NewSessionParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_new_session_parameters_legacy() {
-        let json = r#"{"desired":{},"required":{}}"#;
-        let data = NewSessionParameters::Legacy(LegacyNewSessionParameters {
-            desired: Capabilities::new(),
-            required: Capabilities::new(),
-        });
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_new_session_parameters_spec_and_legacy() {
-        let json = r#"{
-            "capabilities":{
-                "alwaysMatch":{},
-                "firstMatch":[{}]
-            },
-            "desired":{},
-            "required":{}
-        }"#;
-        let data = NewSessionParameters::Spec(SpecNewSessionParameters {
-            alwaysMatch: Capabilities::new(),
-            firstMatch: vec![Capabilities::new()],
-        });
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_send_keys_parameters_with_value() {
-        let json = r#"{"text":"foo"}"#;
-        let data = SendKeysParameters { text: "foo".into() };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_send_keys_parameters_invalid_text_field() {
-        let json = r#"{"text":3}"#;
-
-        assert!(serde_json::from_str::<SendKeysParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_send_keys_parameters_missing_text_field() {
-        let json = r#"{}"#;
-
-        assert!(serde_json::from_str::<SendKeysParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_switch_to_frame_parameters_with_value() {
-        let json = r#"{"id":3}"#;
-        let data = SwitchToFrameParameters {
-            id: Some(FrameId::Short(3)),
+    fn test_window_rect_nullable() {
+        let expected = WindowRectParameters {
+            x: Nullable::Value(0i32),
+            y: Nullable::Null,
+            width: Nullable::Value(2i32),
+            height: Nullable::Null,
         };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_switch_to_frame_parameters_with_optional_null_field() {
-        let json = r#"{"id":null}"#;
-        let data = SwitchToFrameParameters { id: None };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_switch_to_frame_parameters_without_optional_null_field() {
-        let json = r#"{}"#;
-        let data = SwitchToFrameParameters { id: None };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_switch_to_frame_parameters_with_invalid_id_field() {
-        let json = r#"{"id":"3""#;
-
-        assert!(serde_json::from_str::<SwitchToFrameParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_switch_to_window_parameters_with_value() {
-        let json = r#"{"handle":"foo"}"#;
-        let data = SwitchToWindowParameters {
-            handle: "foo".into(),
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_switch_to_window_parameters_invalid_handle_field() {
-        let json = r#"{"handle":3}"#;
-
-        assert!(serde_json::from_str::<SwitchToWindowParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_switch_to_window_parameters_missing_handle_field() {
-        let json = r#"{}"#;
-
-        assert!(serde_json::from_str::<SwitchToWindowParameters>(&json).is_err());
+        let actual = Json::from_str(r#"{"x": 0, "y": null, "width": 2, "height": null}"#).unwrap();
+        assert_eq!(expected, Parameters::from_json(&actual).unwrap());
     }
 
     #[test]
-    fn test_json_take_screenshot_parameters_with_element() {
-        let json = r#"{"element":{"element-6066-11e4-a52e-4f735466cecf":"elem"}}"#;
-        let data = TakeScreenshotParameters {
-            element: Some(WebElement::new("elem".into())),
+    fn test_window_rect_missing_fields() {
+        let expected = WindowRectParameters {
+            x: Nullable::Value(0i32),
+            y: Nullable::Null,
+            width: Nullable::Value(2i32),
+            height: Nullable::Null,
         };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_take_screenshot_parameters_with_optional_null_field() {
-        let json = r#"{"element":null}"#;
-        let data = TakeScreenshotParameters { element: None };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_take_screenshot_parameters_without_optional_null_field() {
-        let json = r#"{}"#;
-        let data = TakeScreenshotParameters { element: None };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_take_screenshot_parameters_with_invalid_element_field() {
-        let json = r#"{"element":"foo"}"#;
-        assert!(serde_json::from_str::<TakeScreenshotParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_timeout_parameters_with_values() {
-        let json = r#"{"implicit":1,"pageLoad":2,"script":3}"#;
-        let data = TimeoutsParameters {
-            implicit: Some(1u64),
-            page_load: Some(2u64),
-            script: Some(3u64),
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_timeout_parameters_with_optional_null_field() {
-        let json = r#"{"implicit":null,"pageLoad":null,"script":null}"#;
-        let data = TimeoutsParameters {
-            implicit: None,
-            page_load: None,
-            script: None,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_timeout_parameters_without_optional_null_field() {
-        let json = r#"{}"#;
-        let data = TimeoutsParameters {
-            implicit: None,
-            page_load: None,
-            script: None,
-        };
-
-        check_deserialize(&json, &data);
+        let actual = Json::from_str(r#"{"x": 0, "width": 2}"#).unwrap();
+        assert_eq!(expected, Parameters::from_json(&actual).unwrap());
     }
 
     #[test]
-    fn test_json_timeout_parameters_with_invalid_implicit_value() {
-        let json = r#"{"implicit":1.1}"#;
-        assert!(serde_json::from_str::<TimeoutsParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_timeout_parameters_with_invalid_page_load_value() {
-        let json = r#"{"pageLoad":1.2}"#;
-        assert!(serde_json::from_str::<TimeoutsParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_timeout_parameters_with_invalid_script_value() {
-        let json = r#"{"script":1.3}"#;
-        assert!(serde_json::from_str::<TimeoutsParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_window_rect_parameters_with_values() {
-        let json = r#"{"x":0,"y":1,"width":2,"height":3}"#;
-        let data = WindowRectParameters {
-            x: Some(0i32),
-            y: Some(1i32),
-            width: Some(2i32),
-            height: Some(3i32),
+    fn test_window_rect_floats() {
+        let expected = WindowRectParameters {
+            x: Nullable::Value(1i32),
+            y: Nullable::Value(2i32),
+            width: Nullable::Value(3i32),
+            height: Nullable::Value(4i32),
         };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_window_rect_parameters_with_optional_null_fields() {
-        let json = r#"{"x":null,"y": null,"width":null,"height":null}"#;
-        let data = WindowRectParameters {
-            x: None,
-            y: None,
-            width: None,
-            height: None,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_window_rect_parameters_without_optional_fields() {
-        let json = r#"{}"#;
-        let data = WindowRectParameters {
-            x: None,
-            y: None,
-            width: None,
-            height: None,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_window_rect_parameters_invalid_values_float() {
-        let json = r#"{"x":1.1,"y":2.2,"width":3.3,"height":4.4}"#;
-        let data = WindowRectParameters {
-            x: Some(1),
-            y: Some(2),
-            width: Some(3),
-            height: Some(4),
-        };
-
-        check_deserialize(&json, &data);
+        let actual = Json::from_str(r#"{"x": 1.1, "y": 2.2, "width": 3.3, "height": 4.4}"#).unwrap();
+        assert_eq!(expected, Parameters::from_json(&actual).unwrap());
     }
 }
--- a/testing/webdriver/src/common.rs
+++ b/testing/webdriver/src/common.rs
@@ -1,169 +1,224 @@
-use serde::ser::{Serialize, Serializer};
+use rustc_serialize::{Encodable, Encoder};
+use rustc_serialize::json::{Json, ToJson};
+use std::collections::BTreeMap;
+
+use error::{WebDriverResult, WebDriverError, ErrorStatus};
 
 pub static ELEMENT_KEY: &'static str = "element-6066-11e4-a52e-4f735466cecf";
 pub static FRAME_KEY: &'static str = "frame-075b-4da1-b6ba-e579c2d3230a";
 pub static WINDOW_KEY: &'static str = "window-fcc6-11e5-b4f8-330a88ab9d7f";
 
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct Cookie {
-    pub name: String,
-    pub value: String,
-    pub path: Option<String>,
-    pub domain: Option<String>,
-    #[serde(default)]
-    pub secure: bool,
-    #[serde(default)]
-    pub httpOnly: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub expiry: Option<Date>,
+#[derive(Clone, Debug, PartialEq, RustcEncodable)]
+pub struct Date(pub u64);
+
+impl Date {
+    pub fn new(timestamp: u64) -> Date {
+        Date(timestamp)
+    }
+}
+
+impl ToJson for Date {
+    fn to_json(&self) -> Json {
+        let &Date(x) = self;
+        x.to_json()
+    }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum Nullable<T: ToJson> {
+    Value(T),
+    Null
 }
 
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct Date(pub u64);
+impl<T: ToJson> Nullable<T> {
+     pub fn is_null(&self) -> bool {
+        match *self {
+            Nullable::Value(_) => false,
+            Nullable::Null => true
+        }
+    }
 
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-#[serde(untagged)]
-pub enum FrameId {
-    Short(u16),
-    #[serde(
-        rename = "element-6066-11e4-a52e-4f735466cecf", serialize_with = "serialize_webelement_id"
-    )]
-    Element(WebElement),
+     pub fn is_value(&self) -> bool {
+        match *self {
+            Nullable::Value(_) => true,
+            Nullable::Null => false
+        }
+    }
+
+    pub fn map<F, U: ToJson>(self, f: F) -> Nullable<U>
+        where F: FnOnce(T) -> U {
+        match self {
+            Nullable::Value(val) => Nullable::Value(f(val)),
+            Nullable::Null => Nullable::Null
+        }
+    }
 }
 
-// TODO(Henrik): Remove when ToMarionette trait has been fixed
-fn serialize_webelement_id<S>(element: &WebElement, serializer: S) -> Result<S::Ok, S::Error>
-where
-    S: Serializer,
-{
-    element.id.serialize(serializer)
+impl<T: ToJson> Nullable<T> {
+    //This is not very pretty
+    pub fn from_json<F: FnOnce(&Json) -> WebDriverResult<T>>(value: &Json, f: F) -> WebDriverResult<Nullable<T>> {
+        if value.is_null() {
+            Ok(Nullable::Null)
+        } else {
+            Ok(Nullable::Value(try!(f(value))))
+        }
+    }
+}
+
+impl<T: ToJson> ToJson for Nullable<T> {
+    fn to_json(&self) -> Json {
+        match *self {
+            Nullable::Value(ref x) => x.to_json(),
+            Nullable::Null => Json::Null
+        }
+    }
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub enum LocatorStrategy {
-    #[serde(rename = "css selector")]
-    CSSSelector,
-    #[serde(rename = "link text")]
-    LinkText,
-    #[serde(rename = "partial link text")]
-    PartialLinkText,
-    #[serde(rename = "tag name")]
-    TagName,
-    #[serde(rename = "xpath")]
-    XPath,
+impl<T: ToJson> Encodable for Nullable<T> {
+    fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
+        match *self {
+            Nullable::Value(ref x) => x.to_json().encode(s),
+            Nullable::Null => s.emit_option_none()
+        }
+    }
 }
 
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+impl<T: ToJson> Into<Option<T>> for Nullable<T> {
+    fn into(self) -> Option<T> {
+        match self {
+            Nullable::Value(val) => Some(val),
+            Nullable::Null => None
+        }
+    }
+}
+
+impl<T: ToJson> From<Option<T>> for Nullable<T> {
+    fn from(option: Option<T>) -> Nullable<T> {
+        match option {
+            Some(val) => Nullable::Value(val),
+            None => Nullable::Null,
+        }
+    }
+}
+
+#[derive(Clone, Debug, PartialEq)]
 pub struct WebElement {
-    #[serde(rename = "element-6066-11e4-a52e-4f735466cecf")]
-    pub id: String,
+    pub id: String
 }
 
 impl WebElement {
     pub fn new(id: String) -> WebElement {
-        WebElement { id: id }
+        WebElement {
+            id: id
+        }
+    }
+
+    pub fn from_json(data: &Json) -> WebDriverResult<WebElement> {
+        let object = try_opt!(data.as_object(),
+                              ErrorStatus::InvalidArgument,
+                              "Could not convert webelement to object");
+        let id_value = try_opt!(object.get(ELEMENT_KEY),
+                                ErrorStatus::InvalidArgument,
+                                "Could not find webelement key");
+
+        let id = try_opt!(id_value.as_string(),
+                          ErrorStatus::InvalidArgument,
+                          "Could not convert web element to string").to_string();
+
+        Ok(WebElement::new(id))
+    }
+}
+
+impl ToJson for WebElement {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert(ELEMENT_KEY.to_string(), self.id.to_json());
+        Json::Object(data)
+    }
+}
+
+impl <T> From<T> for WebElement
+    where T: Into<String> {
+    fn from(data: T) -> WebElement {
+        WebElement::new(data.into())
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use serde_json;
-    use test::check_serialize_deserialize;
-
-    #[test]
-    fn test_json_date() {
-        let json = r#"1234"#;
-        let data = Date(1234);
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_date_invalid() {
-        let json = r#""2018-01-01""#;
-        assert!(serde_json::from_str::<Date>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_frame_id_short() {
-        let json = r#"1234"#;
-        let data = FrameId::Short(1234);
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_frame_id_webelement() {
-        let json = r#""elem""#;
-        let data = FrameId::Element(WebElement::new("elem".into()));
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_frame_id_invalid() {
-        let json = r#"true"#;
-        assert!(serde_json::from_str::<FrameId>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_locator_strategy_css_selector() {
-        let json = r#""css selector""#;
-        let data = LocatorStrategy::CSSSelector;
-
-        check_serialize_deserialize(&json, &data);
-    }
+#[derive(Debug, PartialEq)]
+pub enum FrameId {
+    Short(u16),
+    Element(WebElement),
+    Null
+}
 
-    #[test]
-    fn test_json_locator_strategy_link_text() {
-        let json = r#""link text""#;
-        let data = LocatorStrategy::LinkText;
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_locator_strategy_partial_link_text() {
-        let json = r#""partial link text""#;
-        let data = LocatorStrategy::PartialLinkText;
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_locator_strategy_tag_name() {
-        let json = r#""tag name""#;
-        let data = LocatorStrategy::TagName;
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_locator_strategy_xpath() {
-        let json = r#""xpath""#;
-        let data = LocatorStrategy::XPath;
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_locator_strategy_invalid() {
-        let json = r#""foo""#;
-        assert!(serde_json::from_str::<LocatorStrategy>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_webelement() {
-        let json = r#"{"element-6066-11e4-a52e-4f735466cecf":"elem"}"#;
-        let data = WebElement::new("elem".into());
-
-        check_serialize_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_webelement_invalid() {
-        let data = r#"{"elem-6066-11e4-a52e-4f735466cecf":"elem"}"#;
-        assert!(serde_json::from_str::<WebElement>(&data).is_err());
+impl FrameId {
+    pub fn from_json(data: &Json) -> WebDriverResult<FrameId> {
+        match data {
+            &Json::U64(x) => {
+                if x > u16::max_value() as u64 || x < u16::min_value() as u64 {
+                    return Err(WebDriverError::new(ErrorStatus::NoSuchFrame,
+                                                   "frame id out of range"))
+                };
+                Ok(FrameId::Short(x as u16))
+            },
+            &Json::Null => Ok(FrameId::Null),
+            &Json::Object(_) => Ok(FrameId::Element(
+                try!(WebElement::from_json(data)))),
+            _ => Err(WebDriverError::new(ErrorStatus::NoSuchFrame,
+                                         "frame id has unexpected type"))
+        }
     }
 }
+
+impl ToJson for FrameId {
+    fn to_json(&self) -> Json {
+        match *self {
+            FrameId::Short(x) => {
+                Json::U64(x as u64)
+            },
+            FrameId::Element(ref x) => {
+                Json::String(x.id.clone())
+            },
+            FrameId::Null => {
+                Json::Null
+            }
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum LocatorStrategy {
+    CSSSelector,
+    LinkText,
+    PartialLinkText,
+    TagName,
+    XPath,
+}
+
+impl LocatorStrategy {
+    pub fn from_json(body: &Json) -> WebDriverResult<LocatorStrategy> {
+        match try_opt!(body.as_string(),
+                       ErrorStatus::InvalidArgument,
+                       "Expected locator strategy as string") {
+            "css selector" => Ok(LocatorStrategy::CSSSelector),
+            "link text" => Ok(LocatorStrategy::LinkText),
+            "partial link text" => Ok(LocatorStrategy::PartialLinkText),
+            "tag name" => Ok(LocatorStrategy::TagName),
+            "xpath" => Ok(LocatorStrategy::XPath),
+            x => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                         format!("Unknown locator strategy {}", x)))
+        }
+    }
+}
+
+impl ToJson for LocatorStrategy {
+    fn to_json(&self) -> Json {
+        Json::String(match *self {
+            LocatorStrategy::CSSSelector => "css selector",
+            LocatorStrategy::LinkText => "link text",
+            LocatorStrategy::PartialLinkText => "partial link text",
+            LocatorStrategy::TagName => "tag name",
+            LocatorStrategy::XPath => "xpath"
+        }.to_string())
+    }
+}
--- a/testing/webdriver/src/error.rs
+++ b/testing/webdriver/src/error.rs
@@ -1,13 +1,13 @@
-use base64::DecodeError;
 use hyper::status::StatusCode;
-use serde::ser::{Serialize, Serializer};
-use serde_json;
+use rustc_serialize::base64::FromBase64Error;
+use rustc_serialize::json::{DecoderError, Json, ParserError, ToJson};
 use std::borrow::Cow;
+use std::collections::BTreeMap;
 use std::convert::From;
 use std::error::Error;
 use std::fmt;
 use std::io;
 
 #[derive(Debug, PartialEq)]
 pub enum ErrorStatus {
     /// The [`ElementClick`] command could not be completed because the
@@ -135,25 +135,16 @@ pub enum ErrorStatus {
 
     UnknownPath,
 
     /// Indicates that a [command] that should have executed properly is not
     /// currently supported.
     UnsupportedOperation,
 }
 
-impl Serialize for ErrorStatus {
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: Serializer,
-    {
-        self.error_code().serialize(serializer)
-    }
-}
-
 impl ErrorStatus {
     /// Returns the string serialisation of the error type.
     pub fn error_code(&self) -> &'static str {
         use self::ErrorStatus::*;
         match *self {
             ElementClickIntercepted => "element click intercepted",
             ElementNotInteractable => "element not interactable",
             ElementNotSelectable => "element not selectable",
@@ -173,17 +164,18 @@ impl ErrorStatus {
             NoSuchWindow => "no such window",
             ScriptTimeout => "script timeout",
             SessionNotCreated => "session not created",
             StaleElementReference => "stale element reference",
             Timeout => "timeout",
             UnableToCaptureScreen => "unable to capture screen",
             UnableToSetCookie => "unable to set cookie",
             UnexpectedAlertOpen => "unexpected alert open",
-            UnknownCommand | UnknownError => "unknown error",
+            UnknownCommand |
+            UnknownError => "unknown error",
             UnknownMethod => "unknown method",
             UnknownPath => "unknown command",
             UnsupportedOperation => "unsupported operation",
         }
     }
 
     /// Returns the correct HTTP status code associated with the error type.
     pub fn http_status(&self) -> StatusCode {
@@ -256,74 +248,71 @@ impl From<String> for ErrorStatus {
             "unsupported operation" => UnsupportedOperation,
             _ => UnknownError,
         }
     }
 }
 
 pub type WebDriverResult<T> = Result<T, WebDriverError>;
 
-#[derive(Debug, PartialEq, Serialize)]
-#[serde(remote = "Self")]
+#[derive(Debug)]
 pub struct WebDriverError {
     pub error: ErrorStatus,
     pub message: Cow<'static, str>,
-    #[serde(rename = "stacktrace")]
     pub stack: Cow<'static, str>,
-    #[serde(skip)]
     pub delete_session: bool,
 }
 
-impl Serialize for WebDriverError {
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: Serializer,
-    {
-        #[derive(Serialize)]
-        struct Wrapper<'a> {
-            #[serde(with = "WebDriverError")]
-            value: &'a WebDriverError,
-        }
-
-        Wrapper { value: self }.serialize(serializer)
-    }
-}
-
 impl WebDriverError {
     pub fn new<S>(error: ErrorStatus, message: S) -> WebDriverError
-    where
-        S: Into<Cow<'static, str>>,
+        where S: Into<Cow<'static, str>>
     {
         WebDriverError {
             error: error,
             message: message.into(),
             stack: "".into(),
             delete_session: false,
         }
     }
 
     pub fn new_with_stack<S>(error: ErrorStatus, message: S, stack: S) -> WebDriverError
-    where
-        S: Into<Cow<'static, str>>,
+        where S: Into<Cow<'static, str>>
     {
         WebDriverError {
             error: error,
             message: message.into(),
             stack: stack.into(),
             delete_session: false,
         }
     }
 
     pub fn error_code(&self) -> &'static str {
         self.error.error_code()
     }
 
     pub fn http_status(&self) -> StatusCode {
         self.error.http_status()
     }
+
+    pub fn to_json_string(&self) -> String {
+        self.to_json().to_string()
+    }
+}
+
+impl ToJson for WebDriverError {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("error".into(), self.error_code().to_json());
+        data.insert("message".into(), self.message.to_json());
+        data.insert("stacktrace".into(), self.stack.to_json());
+
+        let mut wrapper = BTreeMap::new();
+        wrapper.insert("value".into(), Json::Object(data));
+        Json::Object(wrapper)
+    }
 }
 
 impl Error for WebDriverError {
     fn description(&self) -> &str {
         self.error_code()
     }
 
     fn cause(&self) -> Option<&Error> {
@@ -332,62 +321,37 @@ impl Error for WebDriverError {
 }
 
 impl fmt::Display for WebDriverError {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         self.message.fmt(f)
     }
 }
 
-impl From<serde_json::Error> for WebDriverError {
-    fn from(err: serde_json::Error) -> WebDriverError {
-        WebDriverError::new(ErrorStatus::InvalidArgument, err.to_string())
+impl From<ParserError> for WebDriverError {
+    fn from(err: ParserError) -> WebDriverError {
+        WebDriverError::new(ErrorStatus::UnknownError, err.description().to_string())
     }
 }
 
 impl From<io::Error> for WebDriverError {
     fn from(err: io::Error) -> WebDriverError {
         WebDriverError::new(ErrorStatus::UnknownError, err.description().to_string())
     }
 }
 
-impl From<DecodeError> for WebDriverError {
-    fn from(err: DecodeError) -> WebDriverError {
+impl From<DecoderError> for WebDriverError {
+    fn from(err: DecoderError) -> WebDriverError {
+        WebDriverError::new(ErrorStatus::UnknownError, err.description().to_string())
+    }
+}
+
+impl From<FromBase64Error> for WebDriverError {
+    fn from(err: FromBase64Error) -> WebDriverError {
         WebDriverError::new(ErrorStatus::UnknownError, err.description().to_string())
     }
 }
 
 impl From<Box<Error>> for WebDriverError {
     fn from(err: Box<Error>) -> WebDriverError {
         WebDriverError::new(ErrorStatus::UnknownError, err.description().to_string())
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use test::check_serialize;
-
-    #[test]
-    fn test_json_webdriver_error() {
-        let json = r#"{"value":{
-            "error":"unknown error",
-            "message":"foo bar",
-            "stacktrace":"foo\nbar"
-        }}"#;
-        let data = WebDriverError {
-            error: ErrorStatus::UnknownError,
-            message: "foo bar".into(),
-            stack: "foo\nbar".into(),
-            delete_session: true,
-        };
-
-        check_serialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_error_status() {
-        let json = format!(r#""unknown error""#);
-        let data = ErrorStatus::UnknownError;
-
-        check_serialize(&json, &data);
-    }
-}
--- a/testing/webdriver/src/httpapi.rs
+++ b/testing/webdriver/src/httpapi.rs
@@ -1,249 +1,99 @@
-use regex::{Captures, Regex};
+use regex::{Regex, Captures};
+use rustc_serialize::json::Json;
 
 use hyper::method::Method;
-use hyper::method::Method::{Delete, Get, Post};
-use serde_json::Value;
+use hyper::method::Method::{Get, Post, Delete};
 
-use command::{VoidWebDriverExtensionCommand, WebDriverCommand, WebDriverExtensionCommand,
-              WebDriverMessage};
-use error::{ErrorStatus, WebDriverError, WebDriverResult};
+use command::{WebDriverCommand, WebDriverMessage, WebDriverExtensionCommand,
+              VoidWebDriverExtensionCommand};
+use error::{WebDriverResult, WebDriverError, ErrorStatus};
 
-fn standard_routes<U: WebDriverExtensionRoute>() -> Vec<(Method, &'static str, Route<U>)> {
-    return vec![
-        (Post, "/session", Route::NewSession),
-        (Delete, "/session/{sessionId}", Route::DeleteSession),
-        (Post, "/session/{sessionId}/url", Route::Get),
-        (Get, "/session/{sessionId}/url", Route::GetCurrentUrl),
-        (Post, "/session/{sessionId}/back", Route::GoBack),
-        (Post, "/session/{sessionId}/forward", Route::GoForward),
-        (Post, "/session/{sessionId}/refresh", Route::Refresh),
-        (Get, "/session/{sessionId}/title", Route::GetTitle),
-        (Get, "/session/{sessionId}/source", Route::GetPageSource),
-        (Get, "/session/{sessionId}/window", Route::GetWindowHandle),
-        (
-            Get,
-            "/session/{sessionId}/window/handles",
-            Route::GetWindowHandles,
-        ),
-        (Delete, "/session/{sessionId}/window", Route::CloseWindow),
-        (
-            Get,
-            "/session/{sessionId}/window/size",
-            Route::GetWindowSize,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/window/size",
-            Route::SetWindowSize,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/window/position",
-            Route::GetWindowPosition,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/window/position",
-            Route::SetWindowPosition,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/window/rect",
-            Route::GetWindowRect,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/window/rect",
-            Route::SetWindowRect,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/window/minimize",
-            Route::MinimizeWindow,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/window/maximize",
-            Route::MaximizeWindow,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/window/fullscreen",
-            Route::FullscreenWindow,
-        ),
-        (Post, "/session/{sessionId}/window", Route::SwitchToWindow),
-        (Post, "/session/{sessionId}/frame", Route::SwitchToFrame),
-        (
-            Post,
-            "/session/{sessionId}/frame/parent",
-            Route::SwitchToParentFrame,
-        ),
-        (Post, "/session/{sessionId}/element", Route::FindElement),
-        (Post, "/session/{sessionId}/elements", Route::FindElements),
-        (
-            Post,
-            "/session/{sessionId}/element/{elementId}/element",
-            Route::FindElementElement,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/element/{elementId}/elements",
-            Route::FindElementElements,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/element/active",
-            Route::GetActiveElement,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/element/{elementId}/displayed",
-            Route::IsDisplayed,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/element/{elementId}/selected",
-            Route::IsSelected,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/element/{elementId}/attribute/{name}",
-            Route::GetElementAttribute,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/element/{elementId}/property/{name}",
-            Route::GetElementProperty,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/element/{elementId}/css/{propertyName}",
-            Route::GetCSSValue,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/element/{elementId}/text",
-            Route::GetElementText,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/element/{elementId}/name",
-            Route::GetElementTagName,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/element/{elementId}/rect",
-            Route::GetElementRect,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/element/{elementId}/enabled",
-            Route::IsEnabled,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/execute/sync",
-            Route::ExecuteScript,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/execute/async",
-            Route::ExecuteAsyncScript,
-        ),
-        (Get, "/session/{sessionId}/cookie", Route::GetCookies),
-        (
-            Get,
-            "/session/{sessionId}/cookie/{name}",
-            Route::GetNamedCookie,
-        ),
-        (Post, "/session/{sessionId}/cookie", Route::AddCookie),
-        (Delete, "/session/{sessionId}/cookie", Route::DeleteCookies),
-        (
-            Delete,
-            "/session/{sessionId}/cookie/{name}",
-            Route::DeleteCookie,
-        ),
-        (Get, "/session/{sessionId}/timeouts", Route::GetTimeouts),
-        (Post, "/session/{sessionId}/timeouts", Route::SetTimeouts),
-        (
-            Post,
-            "/session/{sessionId}/element/{elementId}/click",
-            Route::ElementClick,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/element/{elementId}/tap",
-            Route::ElementTap,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/element/{elementId}/clear",
-            Route::ElementClear,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/element/{elementId}/value",
-            Route::ElementSendKeys,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/alert/dismiss",
-            Route::DismissAlert,
-        ),
-        (
-            Post,
-            "/session/{sessionId}/alert/accept",
-            Route::AcceptAlert,
-        ),
-        (Get, "/session/{sessionId}/alert/text", Route::GetAlertText),
-        (
-            Post,
-            "/session/{sessionId}/alert/text",
-            Route::SendAlertText,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/screenshot",
-            Route::TakeScreenshot,
-        ),
-        (
-            Get,
-            "/session/{sessionId}/element/{elementId}/screenshot",
-            Route::TakeElementScreenshot,
-        ),
-        (Post, "/session/{sessionId}/actions", Route::PerformActions),
-        (
-            Delete,
-            "/session/{sessionId}/actions",
-            Route::ReleaseActions,
-        ),
-        (Get, "/status", Route::Status),
-    ];
+fn standard_routes<U:WebDriverExtensionRoute>() -> Vec<(Method, &'static str, Route<U>)> {
+    return vec![(Post, "/session", Route::NewSession),
+                (Delete, "/session/{sessionId}", Route::DeleteSession),
+                (Post, "/session/{sessionId}/url", Route::Get),
+                (Get, "/session/{sessionId}/url", Route::GetCurrentUrl),
+                (Post, "/session/{sessionId}/back", Route::GoBack),
+                (Post, "/session/{sessionId}/forward", Route::GoForward),
+                (Post, "/session/{sessionId}/refresh", Route::Refresh),
+                (Get, "/session/{sessionId}/title", Route::GetTitle),
+                (Get, "/session/{sessionId}/source", Route::GetPageSource),
+                (Get, "/session/{sessionId}/window", Route::GetWindowHandle),
+                (Get, "/session/{sessionId}/window/handles", Route::GetWindowHandles),
+                (Delete, "/session/{sessionId}/window", Route::CloseWindow),
+                (Get, "/session/{sessionId}/window/size", Route::GetWindowSize),
+                (Post, "/session/{sessionId}/window/size", Route::SetWindowSize),
+                (Get, "/session/{sessionId}/window/position", Route::GetWindowPosition),
+                (Post, "/session/{sessionId}/window/position", Route::SetWindowPosition),
+                (Get, "/session/{sessionId}/window/rect", Route::GetWindowRect),
+                (Post, "/session/{sessionId}/window/rect", Route::SetWindowRect),
+                (Post, "/session/{sessionId}/window/minimize", Route::MinimizeWindow),
+                (Post, "/session/{sessionId}/window/maximize", Route::MaximizeWindow),
+                (Post, "/session/{sessionId}/window/fullscreen", Route::FullscreenWindow),
+                (Post, "/session/{sessionId}/window", Route::SwitchToWindow),
+                (Post, &qu