Bug 1396821 - [geckodriver] Switch webdriver crate from rustc_serialize to serde. draft
authorHenrik Skupin <mail@hskupin.info>
Mon, 11 Jun 2018 17:49:22 -0700
changeset 829696 064cce12ccd7afc629e959da74dd97b19a8f20fa
parent 829303 0fc653e3605c91ffa7808ab220390fe5c2b7a561
child 829697 295bd1f706de0fe70892d8b1c5a5c2eb205295a9
push id118789
push userbmo:hskupin@gmail.com
push dateThu, 16 Aug 2018 12:52:34 +0000
bugs1396821
milestone63.0a1
Bug 1396821 - [geckodriver] Switch webdriver crate from rustc_serialize to serde. Instead use serde. This is the simplest possible conversion using the serde Value type everywhere. The intent is to use the automatically derived deserializers in the future. MozReview-Commit-ID: F25p325gbiC
Cargo.lock
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
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -133,16 +133,24 @@ dependencies = [
  "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-core 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-uds 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "base64"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "base64"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1909,16 +1917,26 @@ 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.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa 0.4.1 (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]]
@@ -2391,21 +2409,25 @@ 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.5.2 (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)",
- "rustc-serialize 0.3.24 (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.22 (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"
@@ -2609,16 +2631,17 @@ dependencies = [
 "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
 "checksum app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9dadc668390b373e73e4abbfc1f07238b09a25858f2f39c06cebc6d8e141d774"
 "checksum arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd1479b7c29641adbd35ff3b5c293922d696a92f25c8c975da3e0acbc87258f"
 "checksum arrayvec 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2f0ef4a9820019a0c91d918918c93dc71d469f581a49b47ddc1d285d4270bbe2"
 "checksum ascii-canvas 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b385d69402821a1c254533a011a312531cbcc0e3e24f19bbb4747a5a2daf37e2"
 "checksum atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb2dcb6e6d35f20276943cc04bb98e538b348d525a04ac79c10021561d202f21"
 "checksum atty 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d0fd4c0631f06448cc45a6bbb3b710ebb7ff8ccb96a0800c994afe23a70d5df2"
 "checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159"
+"checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557"
 "checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9"
 "checksum binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88ceb0d16c4fd0e42876e298d7d3ce3780dd9ebdcbe4199816a32c77e08597ff"
 "checksum bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bda13183df33055cbb84b847becce220d392df502ebe7a4a78d7021771ed94d0"
 "checksum bindgen 0.37.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1b25ab82877ea8fe6ce1ce1f8ac54361f0218bad900af9eb11803994bf67c221"
 "checksum binjs_meta 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fd7ca5635f1c6f94aaef7de76cb834c5920578355ce41dbcaf731b7ebe348518"
 "checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
 "checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f"
 "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
@@ -2783,16 +2806,17 @@ dependencies = [
 "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.22 (registry+https://github.com/rust-lang/crates.io-index)" = "84b8035cabe9b35878adec8ac5fe03d5f6bc97ff6edd7ccb96b44c1276ba390e"
 "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/webdriver/Cargo.toml
+++ b/testing/webdriver/Cargo.toml
@@ -6,15 +6,19 @@ 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"
-rustc-serialize = "0.3"
+serde = "1.0"
+serde_json = "1.0"
+serde_derive = "1.0"
 time = "0.1"
 unicode-segmentation = "1.2"
 url = "1"
--- a/testing/webdriver/src/actions.rs
+++ b/testing/webdriver/src/actions.rs
@@ -1,660 +1,1055 @@
-use command::Parameters;
-use common::{Nullable, WebElement};
-use error::{WebDriverResult, WebDriverError, ErrorStatus};
-use rustc_serialize::json::{ToJson, Json};
+use common::WebElement;
+use serde::de::{self, Deserialize, Deserializer};
+use serde::ser::{Serialize, Serializer};
+use std::default::Default;
 use unicode_segmentation::UnicodeSegmentation;
-use std::collections::BTreeMap;
-use std::default::Default;
 
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct ActionSequence {
-    pub id: Nullable<String>,
-    pub actions: ActionsType
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub id: Option<String>,
+    #[serde(flatten)]
+    pub actions: ActionsType,
 }
 
-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");
-
-        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
-
-        let actions = match type_name {
-            "none" | "key" | "pointer" => try!(ActionsType::from_json(&body)),
-            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
-                                                "Invalid action type"))
-        };
-
-        Ok(ActionSequence {
-            id: id.into(),
-            actions: actions
-        })
+#[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 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(untagged)]
+pub enum NullActionItem {
+    General(GeneralAction),
 }
 
-#[derive(Debug, PartialEq)]
-pub enum ActionsType {
-    Null(Vec<NullActionItem>),
-    Key(Vec<KeyActionItem>),
-    Pointer(PointerActionParameters, Vec<PointerActionItem>)
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(tag = "type")]
+pub enum GeneralAction {
+    #[serde(rename="pause")]
+    Pause(PauseAction),
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct PauseAction {
+    pub duration: u64,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum KeyActionItem {
+    General(GeneralAction),
+    Key(KeyAction),
 }
 
-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()
-                };
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(tag = "type")]
+pub enum KeyAction {
+    #[serde(rename="keyDown")]
+    Down(KeyDownAction),
+    #[serde(rename="keyUp")]
+    Up(KeyUpAction),
+}
 
-                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")
-        }
-    }
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct KeyDownAction {
+    #[serde(deserialize_with = "deserialize_key_action_value")]
+    pub value: String,
 }
 
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct KeyUpAction {
+    #[serde(deserialize_with = "deserialize_key_action_value")]
+    pub value: String,
+}
+
+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")]
 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)]
+#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
 pub struct PointerActionParameters {
-    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)
-    }
+    #[serde(rename = "pointerType")]
+    pub pointer_type: PointerType,
 }
 
-#[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 {
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum PointerActionItem {
     General(GeneralAction),
-    Key(KeyAction)
+    Pointer(PointerAction),
 }
 
-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(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 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 PointerDownAction {
+    pub button: u64,
 }
 
-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 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 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)]
+pub struct PointerUpAction {
+    pub button: u64,
 }
 
-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)]
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 pub enum PointerOrigin {
-    Viewport,
+    #[serde(
+        rename = "element-6066-11e4-a52e-4f735466cecf",
+        serialize_with = "serialize_webelement_id",
+        deserialize_with = "deserialize_webelement_id")]
+    Element(WebElement),
+    #[serde(rename = "pointer")]
     Pointer,
-    Element(WebElement),
-}
-
-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(),
-        }
-    }
+    #[serde(rename = "viewport")]
+    Viewport,
 }
 
 impl Default for PointerOrigin {
     fn default() -> PointerOrigin {
         PointerOrigin::Viewport
     }
 }
 
-#[derive(Debug, PartialEq)]
-pub enum PointerAction {
-    Up(PointerUpAction),
-    Down(PointerDownAction),
-    Move(PointerMoveAction),
-    Cancel
+fn serialize_webelement_id<S>(element: &WebElement, serializer: S) -> Result<S::Ok, S::Error>
+    where S: Serializer
+{
+    element.id.serialize(serializer)
 }
 
-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_webelement_id<'de, D>(deserializer: D) -> Result<WebElement, D::Error>
+    where D: Deserializer<'de>
+{
+    String::deserialize(deserializer).map(|id| WebElement { id })
 }
 
-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,
+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 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");
-
-        Ok(PointerUpAction {
-            button: button
-        })
-    }
-}
-
-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)
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub struct PointerDownAction {
-    pub button: u64,
+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 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");
-
-        Ok(PointerDownAction {
-            button: button
-        })
-    }
-}
+#[cfg(test)]
+mod test {
+    use serde_json;
+    use super::*;
+    use test::{check_deserialize, check_serialize_deserialize};
 
-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_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 } )
+                    )
+                }
+            }
+        };
 
-#[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_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") } )
+                    )
+                }
+            }
         };
 
-        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_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 } )
+                    ),
+                }
+            }
+        };
+
+        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());
+    }
+
+    #[test]
+    fn test_json_action_sequence_actions_null() {
+        let json = r#"{
+            "id": "3",
+            "actions": null
+        }"#;
+
+        assert!(serde_json::from_str::<ActionSequence>(&json).is_err());
+    }
+
+    #[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);
+    }
+
+    #[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") } )
+                )
+            }
+        };
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[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 } ),
+                )
+            }
         };
 
-        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_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};
+
+        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};
+
+        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};
+
+        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());
+    }
 
-        let y = match body.find("y") {
-            Some(y) => {
-                Some(try_opt!(y.as_i64(),
-                              ErrorStatus::InvalidArgument,
-                              "Parameter 'y' was not an integer"))
-            },
-            None => None
-        };
+    #[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
+        }"#;
 
-        Ok(PointerMoveAction {
-            duration: duration.into(),
-            origin: origin.into(),
-            x: x.into(),
-            y: y.into(),
-        })
+        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());
     }
 }
-
-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,15 +1,14 @@
-use command::Parameters;
-use error::{ErrorStatus, WebDriverError, WebDriverResult};
-use rustc_serialize::json::{Json, ToJson};
-use std::collections::BTreeMap;
+use error::{WebDriverResult, WebDriverError, ErrorStatus};
+use std::convert::From;
+use serde_json::{Value, Map};
 use url::Url;
 
-pub type Capabilities = BTreeMap<String, Json>;
+pub type Capabilities = Map<String, Value>;
 
 /// 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
@@ -41,35 +40,35 @@ 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: &BTreeMap<String, Json>,
+        proxy_settings: &Map<String, Value>,
         &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: &Json) -> WebDriverResult<()>;
+    fn validate_custom(&self, name: &str, value: &Value) -> 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: &Json,
+        value: &Value,
         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.
@@ -78,32 +77,47 @@ pub trait CapabilitiesMatching {
     ///
     /// 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>>;
 }
 
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 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 == Json::Null)
+            .filter(|&(_, ref value)| **value == Value::Null)
             .map(|(k, _)| k.clone())
             .collect::<Vec<String>>();
         for key in null_entries {
             capabilities.remove(&key);
         }
 
         for (key, value) in capabilities.iter() {
             match &**key {
@@ -140,19 +154,19 @@ impl SpecNewSessionParameters {
                         browser_capabilities.validate_custom(x, value)?
                     }
                 }
             }
         }
         Ok(capabilities)
     }
 
-    fn validate_page_load_strategy(value: &Json) -> WebDriverResult<()> {
+    fn validate_page_load_strategy(value: &Value) -> WebDriverResult<()> {
         match value {
-            &Json::String(ref x) => {
+            &Value::String(ref x) => {
                 match &**x {
                     "normal" |
                     "eager" |
                     "none" => {},
                     x => {
                         return Err(WebDriverError::new(
                             ErrorStatus::InvalidArgument,
                             format!("Invalid page load strategy: {}", x)))
@@ -160,38 +174,38 @@ impl SpecNewSessionParameters {
                 }
             }
             _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
                                                 "pageLoadStrategy is not a string"))
         }
         Ok(())
     }
 
-    fn validate_proxy(proxy_value: &Json) -> WebDriverResult<()> {
+    fn validate_proxy(proxy_value: &Value) -> 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_string() {
+                "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))),
                 },
 
-                "proxyAutoconfigUrl" => match value.as_string() {
+                "proxyAutoconfigUrl" => match value.as_str() {
                     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"
@@ -214,21 +228,21 @@ impl SpecNewSessionParameters {
                     ErrorStatus::InvalidArgument,
                     format!("Invalid proxy configuration entry: {}", x)))
             }
         }
 
         Ok(())
     }
 
-    fn validate_no_proxy(value: &Json) -> WebDriverResult<()> {
+    fn validate_no_proxy(value: &Value) -> WebDriverResult<()> {
         match value.as_array() {
             Some(hosts) => {
                 for host in hosts {
-                    match host.as_string() {
+                    match host.as_str() {
                         Some(_) => {},
                         None => return Err(WebDriverError::new(
                             ErrorStatus::InvalidArgument,
                             format!("noProxy item is not a string: {}", host)
                         ))
                     }
                 }
             },
@@ -238,18 +252,18 @@ impl SpecNewSessionParameters {
             ))
         }
 
         Ok(())
     }
 
     /// Validate whether a named capability is JSON value is a string containing a host
     /// and possible port
-    fn validate_host(value: &Json, entry: &str) -> WebDriverResult<()> {
-        match value.as_string() {
+    fn validate_host(value: &Value, entry: &str) -> WebDriverResult<()> {
+        match value.as_str() {
             Some(host) => {
                 if host.contains("://") {
                     return Err(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
                         format!("{} must not contain a scheme: {}", entry, host)));
                 }
 
                 // Temporarily add a scheme so the host can be parsed as URL
@@ -273,17 +287,17 @@ impl SpecNewSessionParameters {
                 ErrorStatus::InvalidArgument,
                 format!("{} is not a string: {}", entry, value)
             ))
         }
 
         Ok(())
     }
 
-    fn validate_timeouts(value: &Json) -> WebDriverResult<()> {
+    fn validate_timeouts(value: &Value) -> WebDriverResult<()> {
         let obj = try_opt!(
             value.as_object(),
             ErrorStatus::InvalidArgument,
             "timeouts capability is not an object"
         );
 
         for (key, value) in obj.iter() {
             match &**key {
@@ -308,19 +322,19 @@ impl SpecNewSessionParameters {
                     ))
                 }
             }
         }
 
         Ok(())
     }
 
-    fn validate_unhandled_prompt_behaviour(value: &Json) -> WebDriverResult<()> {
+    fn validate_unhandled_prompt_behaviour(value: &Value) -> WebDriverResult<()> {
         let behaviour = try_opt!(
-            value.as_string(),
+            value.as_str(),
             ErrorStatus::InvalidArgument,
             format!("unhandledPromptBehavior is not a string: {}", value)
         );
 
         match behaviour {
             "accept" |
             "accept and notify" |
             "dismiss" |
@@ -333,101 +347,41 @@ impl SpecNewSessionParameters {
                 ))
             }
         }
 
         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![BTreeMap::new()];
+        let default = vec![Map::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)) {
                     return Err(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
                         "firstMatch key shadowed a value in alwaysMatch",
                     ));
                 }
                 let mut merged = self.alwaysMatch.clone();
-                merged.append(&mut first_match_entry.clone());
+                for (key, value) in first_match_entry.clone().into_iter() {
+                    merged.insert(key, value);
+                }
                 Ok(merged)
             })
             .map(|merged| {
                 merged.and_then(|x| self.validate(x, browser_capabilities))
             })
             .collect::<WebDriverResult<Vec<Capabilities>>>()?;
 
         let selected = merged_capabilities
@@ -438,67 +392,67 @@ impl CapabilitiesMatching for SpecNewSes
                 for (key, value) in merged.iter() {
                     match &**key {
                         "browserName" => {
                             let browserValue = browser_capabilities
                                 .browser_name(merged)
                                 .ok()
                                 .and_then(|x| x);
 
-                            if value.as_string() != browserValue.as_ref().map(|x| &**x) {
+                            if value.as_str() != 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_string().unwrap_or("");
+                            let version_cond = value.as_str().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_string() != browserValue.as_ref().map(|x| &**x) {
+                            if value.as_str() != browserValue.as_ref().map(|x| &**x) {
                                 return None;
                             }
                         }
                         "acceptInsecureCerts" => {
-                            if value.as_boolean().unwrap_or(false) &&
+                            if value.as_bool().unwrap_or(false) &&
                                 !browser_capabilities
                                     .accept_insecure_certs(merged)
                                     .unwrap_or(false)
                             {
                                 return None;
                             }
                         }
                         "setWindowRect" => {
-                            if value.as_boolean().unwrap_or(false) &&
+                            if value.as_bool().unwrap_or(false) &&
                                 !browser_capabilities
                                     .set_window_rect(merged)
                                     .unwrap_or(false)
                             {
                                 return None;
                             }
                         }
                         "proxy" => {
-                            let default = BTreeMap::new();
+                            let default = Map::new();
                             let proxy = value.as_object().unwrap_or(&default);
                             if !browser_capabilities
                                 .accept_proxy(&proxy, merged)
                                 .unwrap_or(false)
                             {
                                 return None;
                             }
                         }
@@ -520,94 +474,192 @@ impl CapabilitiesMatching for SpecNewSes
                 return Some(merged);
             })
             .next()
             .map(|x| x.clone());
         Ok(selected)
     }
 }
 
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 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 = BTreeMap::new();
+        let mut capabilities: Capabilities = Map::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))
     }
 }
 
-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)
-        );
+#[cfg(test)]
+mod tests {
+    use serde_json::{self, Value};
+    use super::*;
+    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);
+    }
 
-        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()
+    #[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 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);
+    }
+
+    #[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(),
         };
 
-        Ok(LegacyNewSessionParameters { desired, required })
+        check_deserialize(&json, &data);
     }
-}
+
+    #[test]
+    fn test_json_spec_legacy_new_session_parameters_desired_null() {
+        let json = r#"{"desired":null,"required":{}}"#;
 
-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)
+        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);
+    }
 
-#[cfg(test)]
-mod tests {
-    use rustc_serialize::json::Json;
-    use super::{SpecNewSessionParameters, WebDriverResult};
+    #[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());
 
-    fn validate_proxy(value: &str) -> WebDriverResult<()> {
-        let data = Json::from_str(value).unwrap();
-        SpecNewSessionParameters::validate_proxy(&data)
+        check_deserialize(&json, &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,18 +1,18 @@
-use actions::{ActionSequence};
-use capabilities::{SpecNewSessionParameters, LegacyNewSessionParameters,
-                   CapabilitiesMatching, BrowserCapabilities, Capabilities};
-use common::{Date, Nullable, WebElement, FrameId, LocatorStrategy};
+use actions::ActionSequence;
+use capabilities::{
+    BrowserCapabilities, Capabilities, CapabilitiesMatching,
+    LegacyNewSessionParameters, SpecNewSessionParameters};
+use common::{Date, WebElement, FrameId, LocatorStrategy};
 use error::{WebDriverResult, WebDriverError, ErrorStatus};
 use httpapi::{Route, WebDriverExtensionRoute, VoidWebDriverExtensionRoute};
 use regex::Captures;
-use rustc_serialize::json;
-use rustc_serialize::json::{ToJson, Json};
-use std::collections::BTreeMap;
+use serde::de::{self, Deserialize, Deserializer};
+use serde_json::{self, Value};
 
 #[derive(Debug, PartialEq)]
 pub enum WebDriverCommand<T: WebDriverExtensionCommand> {
     NewSession(NewSessionParameters),
     DeleteSession,
     Get(GetParameters),
     GetCurrentUrl,
     GoBack,
@@ -60,30 +60,30 @@ pub enum WebDriverCommand<T: WebDriverEx
     ElementSendKeys(WebElement, SendKeysParameters),
     PerformActions(ActionsParameters),
     ReleaseActions,
     DismissAlert,
     AcceptAlert,
     GetAlertText,
     SendAlertText(SendKeysParameters),
     TakeScreenshot,
-    TakeElementScreenshot(WebElement),
+    TakeElementScreenshot(TakeScreenshotParameters),
     Status,
     Extension(T)
 }
 
 pub trait WebDriverExtensionCommand : Clone + Send + PartialEq {
-    fn parameters_json(&self) -> Option<Json>;
+    fn parameters_json(&self) -> Option<Value>;
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub struct VoidWebDriverExtensionCommand;
 
 impl WebDriverExtensionCommand for VoidWebDriverExtensionCommand {
-    fn parameters_json(&self) -> Option<Json> {
+    fn parameters_json(&self) -> Option<Value> {
         panic!("No extensions implemented");
     }
 }
 
 #[derive(Debug, PartialEq)]
 pub struct WebDriverMessage <U: WebDriverExtensionRoute=VoidWebDriverExtensionRoute> {
     pub session_id: Option<String>,
     pub command: WebDriverCommand<U::Command>,
@@ -100,82 +100,71 @@ impl<U: WebDriverExtensionRoute> WebDriv
     }
 
     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 = try!(WebDriverMessage::<U>::decode_body(raw_body, requires_body));
-
+        let body_data = WebDriverMessage::<U>::decode_body(raw_body, requires_body)?;
         let command = match match_type {
             Route::NewSession => {
-                let parameters: NewSessionParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::NewSession(parameters)
+                WebDriverCommand::NewSession(serde_json::from_str(raw_body)?)
             },
             Route::DeleteSession => WebDriverCommand::DeleteSession,
             Route::Get => {
-                let parameters: GetParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::Get(parameters)
+                WebDriverCommand::Get(serde_json::from_str(raw_body)?)
             },
             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 => {
-                let parameters: TimeoutsParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::SetTimeouts(parameters)
+                WebDriverCommand::SetTimeouts(serde_json::from_str(raw_body)?)
             },
             Route::GetWindowRect | Route::GetWindowPosition | Route::GetWindowSize => WebDriverCommand::GetWindowRect,
             Route::SetWindowRect | Route::SetWindowPosition | Route::SetWindowSize => {
-                let parameters: WindowRectParameters = Parameters::from_json(&body_data)?;
-                WebDriverCommand::SetWindowRect(parameters)
+                WebDriverCommand::SetWindowRect(serde_json::from_str(raw_body)?)
             },
             Route::MinimizeWindow => WebDriverCommand::MinimizeWindow,
             Route::MaximizeWindow => WebDriverCommand::MaximizeWindow,
             Route::FullscreenWindow => WebDriverCommand::FullscreenWindow,
             Route::SwitchToWindow => {
-                let parameters: SwitchToWindowParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::SwitchToWindow(parameters)
+                WebDriverCommand::SwitchToWindow(serde_json::from_str(raw_body)?)
             }
             Route::SwitchToFrame => {
-                let parameters: SwitchToFrameParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::SwitchToFrame(parameters)
+                WebDriverCommand::SwitchToFrame(serde_json::from_str(raw_body)?)
             },
             Route::SwitchToParentFrame => WebDriverCommand::SwitchToParentFrame,
             Route::FindElement => {
-                let parameters: LocatorParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::FindElement(parameters)
+                WebDriverCommand::FindElement(serde_json::from_str(raw_body)?)
             },
             Route::FindElements => {
-                let parameters: LocatorParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::FindElements(parameters)
+                WebDriverCommand::FindElements(serde_json::from_str(raw_body)?)
             },
             Route::FindElementElement => {
                 let element_id = try_opt!(params.name("elementId"),
                                           ErrorStatus::InvalidArgument,
                                           "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
-                let parameters: LocatorParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::FindElementElement(element, parameters)
+                WebDriverCommand::FindElementElement(element, serde_json::from_str(raw_body)?)
             },
             Route::FindElementElements => {
                 let element_id = try_opt!(params.name("elementId"),
                                           ErrorStatus::InvalidArgument,
                                           "Missing elementId parameter");
                 let element = WebElement::new(element_id.as_str().into());
-                let parameters: LocatorParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::FindElementElements(element, parameters)
+                WebDriverCommand::FindElementElements(element, serde_json::from_str(raw_body)?)
             },
             Route::GetActiveElement => WebDriverCommand::GetActiveElement,
             Route::IsDisplayed => {
                 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)
@@ -266,872 +255,792 @@ impl<U: WebDriverExtensionRoute> WebDriv
                 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 = WebElement::new(element_id.as_str().into());
-                let parameters: SendKeysParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::ElementSendKeys(element, parameters)
+                WebDriverCommand::ElementSendKeys(element, serde_json::from_str(raw_body)?)
             },
             Route::ExecuteScript => {
-                let parameters: JavascriptCommandParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::ExecuteScript(parameters)
+                WebDriverCommand::ExecuteScript(serde_json::from_str(raw_body)?)
             },
             Route::ExecuteAsyncScript => {
-                let parameters: JavascriptCommandParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::ExecuteAsyncScript(parameters)
+                WebDriverCommand::ExecuteAsyncScript(serde_json::from_str(raw_body)?)
             },
             Route::GetCookies => {
                 WebDriverCommand::GetCookies
             },
             Route::GetNamedCookie => {
                 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)
+                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 => {
-                let parameters: ActionsParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::PerformActions(parameters)
+                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 => {
-                let parameters: SendKeysParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::SendAlertText(parameters)
+                WebDriverCommand::SendAlertText(serde_json::from_str(raw_body)?)
             },
             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)
+                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<Json> {
+    fn decode_body(body: &str, requires_body: bool) -> WebDriverResult<Value> {
         if requires_body {
-            match Json::from_str(body) {
-                Ok(x @ Json::Object(_)) => Ok(x),
+            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(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)))
+                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))
+                    }
                 }
             }
         } else {
-            Ok(Json::Null)
+            Ok(Value::Null)
         }
     }
 }
 
-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)]
+pub struct ActionsParameters {
+    pub actions: Vec<ActionSequence>
+}
 
-            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(),
-        };
+#[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>,
+}
 
-        let mut data = BTreeMap::new();
-        if let Some(parameters) = parameters {
-            data.insert("parameters".to_string(), parameters);
+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,
         }
-        Json::Object(data)
+
+        Wrapper::deserialize(deserializer).map(|wrapper| wrapper.cookie)
     }
 }
 
-pub trait Parameters: Sized {
-    fn from_json(body: &Json) -> WebDriverResult<Self>;
+#[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
 }
 
 /// 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 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))))
+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 ToJson for NewSessionParameters {
-    fn to_json(&self) -> Json {
-        match self {
-            &NewSessionParameters::Spec(ref x) => x.to_json(),
-            &NewSessionParameters::Legacy(ref x) => x.to_json()
-        }
+        let legacy = LegacyNewSessionParameters::deserialize(value).map_err(de::Error::custom)?;
+        Ok(NewSessionParameters::Legacy(legacy))
     }
 }
 
 impl CapabilitiesMatching for NewSessionParameters {
     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::Spec(ref x) =>
+                x.match_browser(browser_capabilities),
             &NewSessionParameters::Legacy(ref x) => x.match_browser(browser_capabilities)
         }
     }
 }
 
-
-#[derive(Debug, PartialEq)]
-pub struct GetParameters {
-    pub url: String
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct SendKeysParameters {
+    pub text: String
 }
 
-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 SwitchToFrameParameters {
+    pub id: Option<FrameId>
 }
 
-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 SwitchToWindowParameters {
+    pub handle: String
 }
 
-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)
-    }
+#[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>,
 }
 
 /// 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)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct WindowRectParameters {
-    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>>
+    #[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>,
 }
 
-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()
-        })
-    }
-}
+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 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
+    Ok(value)
 }
 
-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"));
-        }
-
-        let data = try_opt!(body.find("cookie").and_then(|x| x.as_object()),
-                            ErrorStatus::UnableToSetCookie,
-                            "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
-        };
+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)
+        },
+        None => None
+    };
 
-        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)
-    }
+    Ok(value)
 }
 
 #[cfg(test)]
 mod tests {
-    use rustc_serialize::json::Json;
-    use super::{Nullable, Parameters, WindowRectParameters};
+    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::new(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());
+    }
 
     #[test]
-    fn test_window_rect() {
-        let expected = WindowRectParameters {
-            x: Nullable::Value(0i32),
-            y: Nullable::Value(1i32),
-            width: Nullable::Value(2i32),
-            height: Nullable::Value(3i32),
+    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,
         };
-        let actual = Json::from_str(r#"{"x": 0, "y": 1, "width": 2, "height": 3}"#).unwrap();
-        assert_eq!(expected, Parameters::from_json(&actual).unwrap());
+
+        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());
     }
 
     #[test]
-    fn test_window_rect_nullable() {
-        let expected = WindowRectParameters {
-            x: Nullable::Value(0i32),
-            y: Nullable::Null,
-            width: Nullable::Value(2i32),
-            height: Nullable::Null,
-        };
-        let actual = Json::from_str(r#"{"x": 0, "y": null, "width": 2, "height": null}"#).unwrap();
-        assert_eq!(expected, Parameters::from_json(&actual).unwrap());
+    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)) };
+
+        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());
     }
 
     #[test]
-    fn test_window_rect_missing_fields() {
-        let expected = WindowRectParameters {
-            x: Nullable::Value(0i32),
-            y: Nullable::Null,
-            width: Nullable::Value(2i32),
-            height: Nullable::Null,
+    fn test_json_take_screenshot_parameters_with_element() {
+        let json = r#"{"element":{"element-6066-11e4-a52e-4f735466cecf":"elem"}}"#;
+        let data = TakeScreenshotParameters {
+            element: Some(WebElement { id: "elem".into() }),
         };
-        let actual = Json::from_str(r#"{"x": 0, "width": 2}"#).unwrap();
-        assert_eq!(expected, Parameters::from_json(&actual).unwrap());
+
+        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);
     }
 
     #[test]
-    fn test_window_rect_floats() {
-        let expected = WindowRectParameters {
-            x: Nullable::Value(1i32),
-            y: Nullable::Value(2i32),
-            width: Nullable::Value(3i32),
-            height: Nullable::Value(4i32),
+    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),
         };
-        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());
+
+        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);
     }
 }
--- a/testing/webdriver/src/common.rs
+++ b/testing/webdriver/src/common.rs
@@ -1,224 +1,177 @@
-use rustc_serialize::{Encodable, Encoder};
-use rustc_serialize::json::{Json, ToJson};
-use std::collections::BTreeMap;
-
-use error::{WebDriverResult, WebDriverError, ErrorStatus};
+use serde::ser::{Serialize, Serializer};
 
 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, RustcEncodable)]
+#[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, Serialize, Deserialize)]
 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)]
+#[serde(untagged)]
+pub enum FrameId {
+    Short(u16),
+    #[serde(
+        rename = "element-6066-11e4-a52e-4f735466cecf",
+        serialize_with = "serialize_webelement_id",
+    )]
+    Element(WebElement)
 }
 
-impl<T: ToJson> Nullable<T> {
-     pub fn is_null(&self) -> bool {
-        match *self {
-            Nullable::Value(_) => false,
-            Nullable::Null => true
-        }
-    }
-
-     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()
-        }
-    }
-}
-
-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)]
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 pub struct WebElement {
+    #[serde(rename="element-6066-11e4-a52e-4f735466cecf")]
     pub id: String
 }
 
 impl WebElement {
     pub fn new(id: String) -> WebElement {
         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())
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum FrameId {
-    Short(u16),
-    Element(WebElement),
-    Null
 }
 
-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"))
-        }
+#[cfg(test)]
+mod tests {
+    use serde_json;
+    use super::*;
+    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);
     }
-}
+
+    #[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);
+    }
 
-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
-            }
-        }
+    #[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());
     }
 }
-
-#[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 hyper::status::StatusCode;
-use rustc_serialize::base64::FromBase64Error;
-use rustc_serialize::json::{DecoderError, Json, ParserError, ToJson};
+use base64::DecodeError;
+use serde::ser::{Serialize, Serializer};
+use serde_json;
 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,16 +135,25 @@ 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",
@@ -248,24 +257,42 @@ impl From<String> for ErrorStatus {
             "unsupported operation" => UnsupportedOperation,
             _ => UnknownError,
         }
     }
 }
 
 pub type WebDriverResult<T> = Result<T, WebDriverError>;
 
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Serialize)]
+#[serde(remote = "Self")]
 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>>
     {
         WebDriverError {
             error: error,
             message: message.into(),
             stack: "".into(),
@@ -286,33 +313,16 @@ impl WebDriverError {
 
     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> {
@@ -321,37 +331,62 @@ impl Error for WebDriverError {
 }
 
 impl fmt::Display for WebDriverError {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         self.message.fmt(f)
     }
 }
 
-impl From<ParserError> for WebDriverError {
-    fn from(err: ParserError) -> WebDriverError {
-        WebDriverError::new(ErrorStatus::UnknownError, err.description().to_string())
+impl From<serde_json::Error> for WebDriverError {
+    fn from(err: serde_json::Error) -> WebDriverError {
+        WebDriverError::new(ErrorStatus::InvalidArgument, err.to_string())
     }
 }
 
 impl From<io::Error> for WebDriverError {
     fn from(err: io::Error) -> WebDriverError {
         WebDriverError::new(ErrorStatus::UnknownError, err.description().to_string())
     }
 }
 
-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 {
+impl From<DecodeError> for WebDriverError {
+    fn from(err: DecodeError) -> 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);
+    }
+}
\ No newline at end of file
--- a/testing/webdriver/src/httpapi.rs
+++ b/testing/webdriver/src/httpapi.rs
@@ -1,13 +1,13 @@
 use regex::{Regex, Captures};
-use rustc_serialize::json::Json;
 
 use hyper::method::Method;
 use hyper::method::Method::{Get, Post, Delete};
+use serde_json::Value;
 
 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),
@@ -134,26 +134,26 @@ pub enum Route<U:WebDriverExtensionRoute
     TakeElementScreenshot,
     Status,
     Extension(U),
 }
 
 pub trait WebDriverExtensionRoute : Clone + Send + PartialEq {
     type Command: WebDriverExtensionCommand + 'static;
 
-    fn command(&self, &Captures, &Json) -> WebDriverResult<WebDriverCommand<Self::Command>>;
+    fn command(&self, &Captures, &Value) -> WebDriverResult<WebDriverCommand<Self::Command>>;
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub struct VoidWebDriverExtensionRoute;
 
 impl WebDriverExtensionRoute for VoidWebDriverExtensionRoute {
     type Command = VoidWebDriverExtensionCommand;
 
-    fn command(&self, _:&Captures, _:&Json) -> WebDriverResult<WebDriverCommand<VoidWebDriverExtensionCommand>> {
+    fn command(&self, _:&Captures, _:&Value) -> WebDriverResult<WebDriverCommand<VoidWebDriverExtensionCommand>> {
         panic!("No extensions implemented");
     }
 }
 
 #[derive(Clone, Debug)]
 struct RequestMatcher<U: WebDriverExtensionRoute> {
     method: Method,
     path_regexp: Regex,
--- a/testing/webdriver/src/lib.rs
+++ b/testing/webdriver/src/lib.rs
@@ -1,44 +1,28 @@
 #![allow(non_snake_case)]
 
+extern crate cookie;
+extern crate base64;
+#[macro_use]
+extern crate lazy_static;
 #[macro_use]
 extern crate log;
-extern crate rustc_serialize;
 extern crate hyper;
 extern crate regex;
-extern crate cookie;
+extern crate serde;
+#[macro_use]
+extern crate serde_derive;
+extern crate serde_json;
 extern crate time;
 extern crate url;
 extern crate unicode_segmentation;
 
 #[macro_use] pub mod macros;
 pub mod actions;
 pub mod httpapi;
 pub mod capabilities;
 pub mod command;
 pub mod common;
 pub mod error;
 pub mod server;
 pub mod response;
-
-#[cfg(test)]
-mod nullable_tests {
-    use super::common::Nullable;
-
-    #[test]
-    fn test_nullable_map() {
-        let mut test = Nullable::Value(21);
-
-        assert_eq!(test.map(|x| x << 1), Nullable::Value(42));
-
-        test = Nullable::Null;
-
-        assert_eq!(test.map(|x| x << 1), Nullable::Null);
-    }
-
-    #[test]
-    fn test_nullable_into() {
-        let test: Option<i32> = Nullable::Value(42).into();
-
-        assert_eq!(test, Some(42));
-    }
-}
+pub mod test;
--- a/testing/webdriver/src/macros.rs
+++ b/testing/webdriver/src/macros.rs
@@ -1,8 +1,7 @@
 macro_rules! try_opt {
     ($expr:expr, $err_type:expr, $err_msg:expr) => ({
         match $expr {
             Some(x) => x,
             None => return Err(WebDriverError::new($err_type, $err_msg))
         }
-    })
-}
+    })}
--- a/testing/webdriver/src/response.rs
+++ b/testing/webdriver/src/response.rs
@@ -1,124 +1,91 @@
-use common::{Date, Nullable};
-use cookie;
-use rustc_serialize::json::{self, Json, ToJson};
-use std::collections::BTreeMap;
-use time;
+use common::Cookie;
+use serde::ser::{Serialize, Serializer};
+use serde_json::Value;
 
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Serialize)]
+#[serde(untagged, remote = "Self")]
 pub enum WebDriverResponse {
     CloseWindow(CloseWindowResponse),
     Cookie(CookieResponse),
     Cookies(CookiesResponse),
     DeleteSession,
     ElementRect(ElementRectResponse),
     Generic(ValueResponse),
     NewSession(NewSessionResponse),
     Timeouts(TimeoutsResponse),
     Void,
     WindowRect(WindowRectResponse),
 }
 
-impl WebDriverResponse {
-    pub fn to_json_string(self) -> String {
-        use response::WebDriverResponse::*;
+impl Serialize for WebDriverResponse {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        #[derive(Serialize)]
+        struct Wrapper<'a> {
+            #[serde(with = "WebDriverResponse")]
+            value: &'a WebDriverResponse,
+        }
 
-        let obj = match self {
-            CloseWindow(ref x) => json::encode(&x.to_json()),
-            Cookie(ref x) => json::encode(x),
-            Cookies(ref x) => json::encode(x),
-            DeleteSession => Ok("null".to_string()),
-            ElementRect(ref x) => json::encode(x),
-            Generic(ref x) => json::encode(x),
-            NewSession(ref x) => json::encode(x),
-            Timeouts(ref x) => json::encode(x),
-            Void => Ok("null".to_string()),
-            WindowRect(ref x) => json::encode(&x.to_json()),
-        }.unwrap();
-
-        match self {
-            Generic(_) | Cookie(_) | Cookies(_) => obj,
-            _ => {
-                let mut data = String::with_capacity(11 + obj.len());
-                data.push_str("{\"value\": ");
-                data.push_str(&*obj);
-                data.push_str("}");
-                data
-            }
-        }
+        Wrapper { value: self }.serialize(serializer)
     }
 }
 
-#[derive(Debug, RustcEncodable)]
+#[derive(Debug, PartialEq)]
 pub struct CloseWindowResponse {
     pub window_handles: Vec<String>,
 }
 
+impl Serialize for CloseWindowResponse {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        self.window_handles.serialize(serializer)
+    }
+}
+
 impl CloseWindowResponse {
     pub fn new(handles: Vec<String>) -> CloseWindowResponse {
         CloseWindowResponse { window_handles: handles }
     }
 }
 
-impl ToJson for CloseWindowResponse {
-    fn to_json(&self) -> Json {
-        Json::Array(self.window_handles
-                    .iter()
-                    .map(|x| Json::String(x.clone()))
-                    .collect::<Vec<Json>>())
-    }
+#[derive(Clone, Debug, PartialEq)]
+pub struct CookieResponse {
+    pub cookie: Cookie,
 }
 
-#[derive(Debug, RustcEncodable)]
-pub struct NewSessionResponse {
-    pub sessionId: String,
-    pub capabilities: json::Json
-}
-
-impl NewSessionResponse {
-    pub fn new(session_id: String, capabilities: json::Json) -> NewSessionResponse {
-        NewSessionResponse {
-            capabilities: capabilities,
-            sessionId: session_id
-        }
+impl Serialize for CookieResponse {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        self.cookie.serialize(serializer)
     }
 }
 
-#[derive(Debug, RustcEncodable)]
-pub struct TimeoutsResponse {
-    pub script: u64,
-    pub pageLoad: u64,
-    pub implicit: u64,
+#[derive(Debug, PartialEq)]
+pub struct CookiesResponse {
+    pub cookies: Vec<Cookie>,
 }
 
-impl TimeoutsResponse {
-    pub fn new(script: u64, page_load: u64, implicit: u64) -> TimeoutsResponse {
-        TimeoutsResponse {
-            script: script,
-            pageLoad: page_load,
-            implicit: implicit,
-        }
+impl Serialize for CookiesResponse {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        self.cookies.serialize(serializer)
     }
 }
 
-#[derive(Debug, RustcEncodable)]
-pub struct ValueResponse {
-    pub value: json::Json
-}
-
-impl ValueResponse {
-    pub fn new(value: json::Json) -> ValueResponse {
-        ValueResponse {
-            value: value
-        }
-    }
-}
-
-#[derive(Debug, RustcEncodable)]
+#[derive(Debug, PartialEq, Serialize)]
 pub struct ElementRectResponse {
     /// X axis position of the top-left corner of the element relative
     // to the current browsing context’s document element in CSS reference
     // pixels.
     pub x: f64,
 
     /// Y axis position of the top-left corner of the element relative
     // to the current browsing context’s document element in CSS reference
@@ -133,17 +100,82 @@ pub struct ElementRectResponse {
 
     /// Width of the element’s [bounding rectangle] in CSS reference
     /// pixels.
     ///
     /// [bounding rectangle]: https://drafts.fxtf.org/geometry/#rectangle
     pub height: f64,
 }
 
-#[derive(Debug)]
+impl ElementRectResponse {
+    pub fn new(x: f64, y: f64, width: f64, height: f64) -> ElementRectResponse {
+        ElementRectResponse {
+            x: x,
+            y: y,
+            width: width,
+            height: height,
+        }
+    }
+}
+
+#[derive(Debug, PartialEq, Serialize)]
+pub struct NewSessionResponse {
+    pub sessionId: String,
+    pub capabilities: Value
+}
+
+impl NewSessionResponse {
+    pub fn new(session_id: String, capabilities: Value) -> NewSessionResponse {
+        NewSessionResponse {
+            capabilities: capabilities,
+            sessionId: session_id
+        }
+    }
+}
+
+#[derive(Debug, PartialEq, Serialize)]
+pub struct TimeoutsResponse {
+    pub script: u64,
+    pub pageLoad: u64,
+    pub implicit: u64,
+}
+
+impl TimeoutsResponse {
+    pub fn new(script: u64, page_load: u64, implicit: u64) -> TimeoutsResponse {
+        TimeoutsResponse {
+            script: script,
+            pageLoad: page_load,
+            implicit: implicit,
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct ValueResponse {
+    pub value: Value
+}
+
+impl Serialize for ValueResponse {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        self.value.serialize(serializer)
+    }
+}
+
+impl ValueResponse {
+    pub fn new(value: Value) -> ValueResponse {
+        ValueResponse {
+            value: value
+        }
+    }
+}
+
+#[derive(Debug, PartialEq, Serialize)]
 pub struct WindowRectResponse {
     /// `WindowProxy`’s [screenX] attribute.
     ///
     /// [screenX]: https://drafts.csswg.org/cssom-view/#dom-window-screenx
     pub x: i32,
 
     /// `WindowProxy`’s [screenY] attribute.
     ///
@@ -156,175 +188,176 @@ pub struct WindowRectResponse {
     pub width: i32,
 
     /// Height of the top-level browsing context’s outer dimensions, including
     /// any browser chrome and externally drawn window decorations in CSS
     /// reference pixels.
     pub height: i32,
 }
 
-impl ToJson for WindowRectResponse {
-    fn to_json(&self) -> Json {
-        let mut body = BTreeMap::new();
-        body.insert("x".to_owned(), self.x.to_json());
-        body.insert("y".to_owned(), self.y.to_json());
-        body.insert("width".to_owned(), self.width.to_json());
-        body.insert("height".to_owned(), self.height.to_json());
-        Json::Object(body)
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, RustcEncodable)]
-pub struct Cookie {
-    pub name: String,
-    pub value: String,
-    pub path: Nullable<String>,
-    pub domain: Nullable<String>,
-    pub expiry: Nullable<Date>,
-    pub secure: bool,
-    pub httpOnly: bool,
-}
-
-impl Into<cookie::Cookie<'static>> for Cookie {
-    fn into(self) -> cookie::Cookie<'static> {
-        let cookie = cookie::Cookie::build(self.name, self.value)
-            .secure(self.secure)
-            .http_only(self.httpOnly);
-        let cookie = match self.domain {
-            Nullable::Value(domain) => cookie.domain(domain),
-            Nullable::Null => cookie,
-        };
-        let cookie = match self.path {
-            Nullable::Value(path) => cookie.path(path),
-            Nullable::Null => cookie,
-        };
-        let cookie = match self.expiry {
-            Nullable::Value(Date(expiry)) => {
-                cookie.expires(time::at(time::Timespec::new(expiry as i64, 0)))
-            }
-            Nullable::Null => cookie,
-        };
-        cookie.finish()
-    }
-}
-
-#[derive(Debug, RustcEncodable)]
-pub struct CookieResponse {
-    pub value: Cookie,
-}
-
-#[derive(Debug, RustcEncodable)]
-pub struct CookiesResponse {
-    pub value: Vec<Cookie>,
-}
-
 #[cfg(test)]
 mod tests {
-    use super::{CloseWindowResponse, Cookie, CookieResponse, CookiesResponse, ElementRectResponse,
-                NewSessionResponse, Nullable, TimeoutsResponse, ValueResponse, WebDriverResponse,
-                WindowRectResponse};
-    use rustc_serialize::json::Json;
-    use std::collections::BTreeMap;
+    use common::Date;
+    use serde_json;
+    use super::*;
+    use test::check_serialize;
 
-    fn test(resp: WebDriverResponse, expected_str: &str) {
-        let data = resp.to_json_string();
-        let actual = Json::from_str(&*data).unwrap();
-        let expected = Json::from_str(expected_str).unwrap();
-        assert_eq!(actual, expected);
+    #[test]
+    fn test_json_close_window_response() {
+        let json = r#"{"value":["1234"]}"#;
+        let data = WebDriverResponse::CloseWindow(
+            CloseWindowResponse::new(vec!["1234".into()]));
+
+        check_serialize(&json, &data);
     }
 
     #[test]
-    fn test_close_window() {
-        let resp = WebDriverResponse::CloseWindow(
-            CloseWindowResponse::new(vec!["test".into()]));
-        let expected = r#"{"value": ["test"]}"#;
-        test(resp, expected);
+    fn test_json_cookie_response_with_optional() {
+        let json = r#"{"value":{
+            "name":"foo",
+            "value":"bar",
+            "path":"/",
+            "domain":"foo.bar",
+            "secure":true,
+            "httpOnly":false,
+            "expiry":123
+        }}"#;
+        let data = WebDriverResponse::Cookie(CookieResponse { cookie: Cookie {
+            name: "foo".into(),
+            value: "bar".into(),
+            path: Some("/".into()),
+            domain: Some("foo.bar".into()),
+            expiry: Some(Date::new(123)),
+            secure: true,
+            httpOnly: false,
+        }});
+
+        check_serialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_cookie_response_without_optional() {
+        let json = r#"{"value":{
+            "name":"foo",
+            "value":"bar",
+            "path":"/",
+            "domain":null,
+            "secure":true,
+            "httpOnly":false
+        }}"#;
+        let data = WebDriverResponse::Cookie(CookieResponse { cookie: Cookie {
+            name: "foo".into(),
+            value: "bar".into(),
+            path: Some("/".into()),
+            domain: None,
+            expiry: None,
+            secure: true,
+            httpOnly: false,
+        }});
+
+        check_serialize(&json, &data);
     }
 
     #[test]
-    fn test_cookie() {
-        let cookie = Cookie {
-            name: "name".into(),
-            value: "value".into(),
-            path: Nullable::Value("/".into()),
-            domain: Nullable::Null,
-            expiry: Nullable::Null,
-            secure: true,
-            httpOnly: false,
-        };
-        let resp = WebDriverResponse::Cookie(CookieResponse { value: cookie });
-        let expected = r#"{"value": {"name": "name", "expiry": null, "value": "value",
-"path": "/", "domain": null, "secure": true, "httpOnly": false}}"#;
-        test(resp, expected);
+    fn test_json_cookies_response() {
+        let json = r#"{"value":[{
+            "name":"name",
+            "value":"value",
+            "path":"/",
+            "domain":null,
+            "secure":true,
+            "httpOnly":false
+        }]}"#;
+        let data = WebDriverResponse::Cookies(CookiesResponse {
+            cookies: vec![
+                Cookie {
+                    name: "name".into(),
+                    value: "value".into(),
+                    path: Some("/".into()),
+                    domain: None,
+                    expiry: None,
+                    secure: true,
+                    httpOnly: false,
+                }
+            ]
+        });
+
+        check_serialize(&json, &data);
     }
 
     #[test]
-    fn test_cookies() {
-        let resp = WebDriverResponse::Cookies(CookiesResponse {
-            value: vec![
-                Cookie {
-                    name: "name".into(),
-                    value: "value".into(),
-                    path: Nullable::Value("/".into()),
-                    domain: Nullable::Null,
-                    expiry: Nullable::Null,
-                    secure: true,
-                    httpOnly: false,
-                }
-            ]});
-        let expected = r#"{"value": [{"name": "name", "value": "value", "path": "/",
-"domain": null, "expiry": null, "secure": true, "httpOnly": false}]}"#;
-        test(resp, expected);
+    fn test_json_delete_session_response() {
+        let json = r#"{"value":null}"#;
+        let data = WebDriverResponse::DeleteSession;
+
+        check_serialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_element_rect_response() {
+        let json = r#"{"value":{"x":0.0,"y":1.0,"width":2.0,"height":3.0}}"#;
+        let data = WebDriverResponse::ElementRect(
+            ElementRectResponse {
+                x: 0f64,
+                y: 1f64,
+                width: 2f64,
+                height: 3f64,
+            });
+
+        check_serialize(&json, &data);
     }
 
     #[test]
-    fn test_element_rect() {
-        let rect = ElementRectResponse {
-            x: 0f64,
-            y: 1f64,
-            width: 2f64,
-            height: 3f64,
-        };
-        let resp = WebDriverResponse::ElementRect(rect);
-        let expected = r#"{"value": {"x": 0.0, "y": 1.0, "width": 2.0, "height": 3.0}}"#;
-        test(resp, expected);
+    fn test_json_generic_value_response() {
+        let json = r#"{"value":{"example":["test"]}}"#;
+        let mut value = serde_json::Map::new();
+        value.insert("example".into(), Value::Array(
+            vec![Value::String("test".into())]));
+
+        let data = WebDriverResponse::Generic(
+            ValueResponse::new(Value::Object(value)));
+
+        check_serialize(&json, &data);
     }
 
     #[test]
-    fn test_window_rect() {
-        let rect = WindowRectResponse {
-            x: 0i32,
-            y: 1i32,
-            width: 2i32,
-            height: 3i32,
-        };
-        let resp = WebDriverResponse::WindowRect(rect);
-        let expected = r#"{"value": {"x": 0, "y": 1, "width": 2, "height": 3}}"#;
-        test(resp, expected);
+    fn test_json_new_session_response() {
+        let json = r#"{"value":{"sessionId":"id","capabilities":{}}}"#;
+        let data = WebDriverResponse::NewSession(
+            NewSessionResponse::new(
+                "id".into(),
+                Value::Object(serde_json::Map::new())
+        ));
+
+        check_serialize(&json, &data);
     }
 
     #[test]
-    fn test_new_session() {
-        let resp = WebDriverResponse::NewSession(
-            NewSessionResponse::new("test".into(),
-                                    Json::Object(BTreeMap::new())));
-        let expected = r#"{"value": {"sessionId": "test", "capabilities": {}}}"#;
-        test(resp, expected);
+    fn test_json_timeouts_response() {
+        let json = r#"{"value":{"script":1,"pageLoad":2,"implicit":3}}"#;
+        let data = WebDriverResponse::Timeouts(
+            TimeoutsResponse::new(1, 2, 3));
+
+        check_serialize(&json, &data);
     }
 
     #[test]
-    fn test_timeouts() {
-         let resp = WebDriverResponse::Timeouts(TimeoutsResponse::new(
-            1, 2, 3));
-        let expected = r#"{"value": {"script": 1, "pageLoad": 2, "implicit": 3}}"#;
-        test(resp, expected);
+    fn test_json_void_response() {
+        let json = r#"{"value":null}"#;
+        let data = WebDriverResponse::Void;
+
+        check_serialize(&json, &data);
     }
 
     #[test]
-    fn test_value() {
-        let mut value = BTreeMap::new();
-        value.insert("example".into(), Json::Array(vec![Json::String("test".into())]));
-        let resp = WebDriverResponse::Generic(ValueResponse::new(
-            Json::Object(value)));
-        let expected = r#"{"value": {"example": ["test"]}}"#;
-        test(resp, expected);
+    fn test_json_window_rect_response() {
+        let json = r#"{"value":{"x":0,"y":1,"width":2,"height":3}}"#;
+        let data = WebDriverResponse::WindowRect(
+            WindowRectResponse {
+                x: 0i32,
+                y: 1i32,
+                width: 2i32,
+                height: 3i32,
+            });
+
+        check_serialize(&json, &data);
     }
 }
--- a/testing/webdriver/src/server.rs
+++ b/testing/webdriver/src/server.rs
@@ -1,8 +1,9 @@
+use serde_json;
 use std::io::Read;
 use std::marker::PhantomData;
 use std::net::SocketAddr;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::sync::Mutex;
 use std::thread;
 use std::time::Duration;
 
@@ -205,27 +206,30 @@ impl<U: WebDriverExtensionRoute> Handler
                             Err(_) => {
                                 error!("Something terrible happened");
                                 return;
                             }
                         }
                         match recv_res.recv() {
                             Ok(data) => {
                                 match data {
-                                    Ok(response) => (StatusCode::Ok, response.to_json_string()),
-                                    Err(err) => (err.http_status(), err.to_json_string()),
+                                    Ok(response) => (StatusCode::Ok,
+                                                     serde_json::to_string_pretty(&response).unwrap()),
+                                    Err(err) => (err.http_status(),
+                                                 serde_json::to_string_pretty(&err).unwrap()),
                                 }
                             }
                             Err(e) => panic!("Error reading response: {:?}", e),
                         }
                     }
-                    Err(err) => (err.http_status(), err.to_json_string()),
+                    Err(err) => (err.http_status(),
+                                 serde_json::to_string_pretty(&err).unwrap()),
                 };
 
-                debug!("<- {} {}", status, resp_body);
+                debug!("<- {} {:?}", status, resp_body);
 
                 {
                     let resp_status = res.status_mut();
                     *resp_status = status;
                 }
                 res.headers_mut()
                     .set(ContentType(Mime(TopLevel::Application,
                                           SubLevel::Json,
new file mode 100644
--- /dev/null
+++ b/testing/webdriver/src/test.rs
@@ -0,0 +1,51 @@
+use regex::Regex;
+use serde;
+use serde_json;
+use std;
+
+pub fn check_serialize_deserialize<T>(json: &str, data: &T)
+where
+    T: std::fmt::Debug,
+    T: std::cmp::PartialEq,
+    T: serde::de::DeserializeOwned,
+    T: serde::Serialize,
+{
+    lazy_static! {
+        static ref MIN_REGEX: Regex = Regex::new(r"[\n\t]|\s{4}").unwrap();
+    }
+
+    let min_json = MIN_REGEX.replace_all(json, "");
+
+    assert_eq!(*data, serde_json::from_str::<T>(&min_json).unwrap());
+    assert_eq!(min_json, serde_json::to_string(data).unwrap());
+}
+
+pub fn check_deserialize<T>(json: &str, data: &T)
+where
+    T: std::fmt::Debug,
+    T: std::cmp::PartialEq,
+    T: serde::de::DeserializeOwned,
+{
+    lazy_static! {
+        static ref MIN_REGEX: Regex = Regex::new(r"[\n\t]|\s{4}").unwrap();
+    }
+
+    let min_json = MIN_REGEX.replace_all(json, "");
+
+    assert_eq!(serde_json::from_str::<T>(&min_json).unwrap(), *data);
+}
+
+pub fn check_serialize<T>(json: &str, data: &T)
+where
+    T: std::fmt::Debug,
+    T: std::cmp::PartialEq,
+    T: serde::Serialize,
+{
+    lazy_static! {
+        static ref MIN_REGEX: Regex = Regex::new(r"[\n\t]|\s{4}").unwrap();
+    }
+
+    let min_json = MIN_REGEX.replace_all(json, "");
+
+    assert_eq!(min_json, serde_json::to_string(data).unwrap());
+}