Bug 1396821 - [geckodriver] Switch webdriver crate from rustc_serialize to serde. r=ato
☠☠ backed out by d8a7212c51a4 ☠ ☠
authorHenrik Skupin <mail@hskupin.info>
Mon, 11 Jun 2018 17:49:22 -0700
changeset 480930 b61ce753f01e0c1a2ae06c58058880e653a857f0
parent 480929 7ef3912feb2c844edabbdb6376838d046432bc22
child 480931 7c97853a85b94dc65cfda23a8ee6f826a606505d
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersato
bugs1396821
milestone63.0a1
Bug 1396821 - [geckodriver] Switch webdriver crate from rustc_serialize to serde. r=ato 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
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/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,1048 @@
-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");
+#[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>,
+    },
+}
 
-        let id = match data.get("id") {
-            Some(x) => Some(try_opt!(x.as_string(),
-                                     ErrorStatus::InvalidArgument,
-                                     "Parameter 'id' was not a string").to_owned()),
-            None => None
-        };
-
-
-        // Note that unlike the spec we get the pointer parameters in ActionsType::from_json
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum NullActionItem {
+    General(GeneralAction),
+}
 
-        let actions = match type_name {
-            "none" | "key" | "pointer" => try!(ActionsType::from_json(&body)),
-            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
-                                                "Invalid action type"))
-        };
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(tag = "type")]
+pub enum GeneralAction {
+    #[serde(rename = "pause")]
+    Pause(PauseAction),
+}
 
-        Ok(ActionSequence {
-            id: id.into(),
-            actions: actions
-        })
-    }
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct PauseAction {
+    pub duration: u64,
 }
 
-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 KeyActionItem {
+    General(GeneralAction),
+    Key(KeyAction),
 }
 
-#[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 KeyAction {
+    #[serde(rename = "keyDown")]
+    Down(KeyDownAction),
+    #[serde(rename = "keyUp")]
+    Up(KeyUpAction),
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct KeyDownAction {
+    #[serde(deserialize_with = "deserialize_key_action_value")]
+    pub value: String,
 }
 
-impl Parameters for ActionsType {
-    fn from_json(body: &Json) -> WebDriverResult<ActionsType> {
-        // These unwraps are OK as long as this is only called from ActionSequence::from_json
-        let data = body.as_object().expect("Body should be a JSON Object");
-        let actions_type = body.find("type").and_then(|x| x.as_string()).expect("Type should be a string");
-        let actions_chain = try_opt!(try_opt!(data.get("actions"),
-                                              ErrorStatus::InvalidArgument,
-                                              "Missing actions parameter").as_array(),
-                                     ErrorStatus::InvalidArgument,
-                                     "Parameter 'actions' was not an array");
-        match actions_type {
-            "none" => {
-                let mut actions = Vec::with_capacity(actions_chain.len());
-                for action_body in actions_chain.iter() {
-                    actions.push(try!(NullActionItem::from_json(action_body)));
-                };
-                Ok(ActionsType::Null(actions))
-            },
-            "key" => {
-                let mut actions = Vec::with_capacity(actions_chain.len());
-                for action_body in actions_chain.iter() {
-                    actions.push(try!(KeyActionItem::from_json(action_body)));
-                };
-                Ok(ActionsType::Key(actions))
-            },
-            "pointer" => {
-                let mut actions = Vec::with_capacity(actions_chain.len());
-                let parameters = match data.get("parameters") {
-                    Some(x) => try!(PointerActionParameters::from_json(x)),
-                    None => Default::default()
-                };
-
-                for action_body in actions_chain.iter() {
-                    actions.push(try!(PointerActionItem::from_json(action_body)));
-                }
-                Ok(ActionsType::Pointer(parameters, actions))
-            }
-            _ => panic!("Got unexpected action type after checking type")
-        }
-    }
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct KeyUpAction {
+    #[serde(deserialize_with = "deserialize_key_action_value")]
+    pub value: String,
 }
 
-#[derive(Debug, PartialEq)]
+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");
+#[cfg(test)]
+mod test {
+    use super::*;
+    use serde_json;
+    use test::{check_deserialize, check_serialize_deserialize};
+
+    #[test]
+    fn test_json_action_sequence_null() {
+        let json = r#"{
+            "id":"none",
+            "type":"none",
+            "actions":[{
+                "type":"pause","duration":1
+            }]
+        }"#;
+        let data = ActionSequence {
+            id: Some("none".into()),
+            actions: ActionsType::Null {
+                actions: vec![NullActionItem::General(GeneralAction::Pause(PauseAction {
+                    duration: 1,
+                }))],
+            },
+        };
+
+        check_serialize_deserialize(&json, &data);
+    }
 
-        Ok(PointerDownAction {
-            button: button
-        })
+    #[test]
+    fn test_json_action_sequence_key() {
+        let json = r#"{
+            "id":"some_key",
+            "type":"key",
+            "actions":[
+                {"type":"keyDown","value":"f"}
+            ]
+        }"#;
+        let data = ActionSequence {
+            id: Some("some_key".into()),
+            actions: ActionsType::Key {
+                actions: vec![KeyActionItem::Key(KeyAction::Down(KeyDownAction {
+                    value: String::from("f"),
+                }))],
+            },
+        };
+
+        check_serialize_deserialize(&json, &data);
     }
-}
+
+    #[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 })),
+                ],
+            },
+        };
 
-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)
+        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());
+    }
 
-#[derive(Debug, PartialEq)]
-pub struct PointerMoveAction {
-    pub duration: Nullable<u64>,
-    pub origin: PointerOrigin,
-    pub x: Nullable<i64>,
-    pub y: Nullable<i64>
-}
+    #[test]
+    fn test_json_actions_type_null() {
+        let json = r#"{
+            "type":"none",
+            "actions":[{
+                "type":"pause",
+                "duration":1
+            }]
+        }"#;
+        let data = ActionsType::Null {
+            actions: vec![NullActionItem::General(GeneralAction::Pause(PauseAction {
+                duration: 1,
+            }))],
+        };
+
+        check_serialize_deserialize(&json, &data);
+    }
 
-impl Parameters for PointerMoveAction {
-    fn from_json(body: &Json) -> WebDriverResult<PointerMoveAction> {
-        let duration = match body.find("duration") {
-            Some(duration) => Some(try_opt!(duration.as_u64(),
-                                            ErrorStatus::InvalidArgument,
-                                            "Parameter 'duration' was not a positive integer")),
-            None => None
+    #[test]
+    fn test_json_actions_type_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 origin = match body.find("origin") {
-            Some(o) => try!(PointerOrigin::from_json(o)),
-            None => PointerOrigin::default()
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_actions_type_invalid() {
+        let json = r#"{"actions":[{"foo":"bar"}]}"#;
+        assert!(serde_json::from_str::<ActionsType>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_null_action_item_general() {
+        let json = r#"{"type":"pause","duration":1}"#;
+        let data = NullActionItem::General(GeneralAction::Pause(PauseAction { duration: 1 }));
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_null_action_item_invalid_type() {
+        let json = r#"{"type":"invalid"}"#;
+        assert!(serde_json::from_str::<NullActionItem>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_general_action_pause() {
+        let json = r#"{"type":"pause","duration":1}"#;
+        let data = GeneralAction::Pause(PauseAction { duration: 1 });
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_general_action_pause_with_duration_missing() {
+        let json = r#"{"type":"pause"}"#;
+
+        assert!(serde_json::from_str::<GeneralAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_general_action_pause_with_duration_null() {
+        let json = r#"{"type":"pause","duration":null}"#;
+
+        assert!(serde_json::from_str::<GeneralAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_general_action_pause_with_duration_invalid_type() {
+        let json = r#"{"type":"pause","duration":"foo"}"#;
+
+        assert!(serde_json::from_str::<GeneralAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_general_action_pause_with_duration_negative() {
+        let json = r#"{"type":"pause","duration":-30}"#;
+
+        assert!(serde_json::from_str::<GeneralAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_key_action_item_general() {
+        let json = r#"{"type":"pause","duration":1}"#;
+        let data = KeyActionItem::General(GeneralAction::Pause(PauseAction { duration: 1 }));
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_key_action_item_key() {
+        let json = r#"{"type":"keyDown","value":"f"}"#;
+        let data = KeyActionItem::Key(KeyAction::Down(KeyDownAction {
+            value: String::from("f"),
+        }));
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_key_action_item_invalid_type() {
+        let json = r#"{"type":"invalid"}"#;
+        assert!(serde_json::from_str::<KeyActionItem>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_key_action_missing_subtype() {
+        let json = r#"{"value":"f"}"#;
+
+        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_key_action_wrong_subtype() {
+        let json = r#"{"type":"pause","value":"f"}"#;
+
+        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_key_action_down() {
+        let json = r#"{"type":"keyDown","value":"f"}"#;
+        let data = KeyAction::Down(KeyDownAction {
+            value: "f".to_owned(),
+        });
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_key_action_down_with_value_unicode() {
+        let json = r#"{"type":"keyDown","value":"à"}"#;
+        let data = KeyAction::Down(KeyDownAction {
+            value: "à".to_owned(),
+        });
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_key_action_down_with_value_unicode_encoded() {
+        let json = r#"{"type":"keyDown","value":"\u00E0"}"#;
+        let data = KeyAction::Down(KeyDownAction {
+            value: "à".to_owned(),
+        });
+
+        check_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_key_action_down_with_value_missing() {
+        let json = r#"{"type":"keyDown"}"#;
+
+        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_key_action_down_with_value_null() {
+        let json = r#"{"type":"keyDown","value":null}"#;
+
+        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_key_action_down_with_value_invalid_type() {
+        let json = r#"{"type":"keyDown,"value":["f","o","o"]}"#;
+
+        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_key_action_down_with_multiple_code_points() {
+        let json = r#"{"type":"keyDown","value":"fo"}"#;
+
+        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_key_action_up() {
+        let json = r#"{"type":"keyUp","value":"f"}"#;
+        let data = KeyAction::Up(KeyUpAction {
+            value: "f".to_owned(),
+        });
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_key_action_up_with_value_unicode() {
+        let json = r#"{"type":"keyUp","value":"à"}"#;
+        let data = KeyAction::Up(KeyUpAction {
+            value: "à".to_owned(),
+        });
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_key_action_up_with_value_unicode_encoded() {
+        let json = r#"{"type":"keyUp","value":"\u00E0"}"#;
+        let data = KeyAction::Up(KeyUpAction {
+            value: "à".to_owned(),
+        });
+
+        check_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_key_action_up_with_value_missing() {
+        let json = r#"{"type":"keyUp"}"#;
+
+        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_key_action_up_with_value_null() {
+        let json = r#"{"type":"keyUp,"value":null}"#;
+
+        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_key_action_up_with_value_invalid_type() {
+        let json = r#"{"type":"keyUp,"value":["f","o","o"]}"#;
+
+        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_key_action_up_with_multiple_code_points() {
+        let json = r#"{"type":"keyUp","value":"fo"}"#;
+
+        assert!(serde_json::from_str::<KeyAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_item_general() {
+        let json = r#"{"type":"pause","duration":1}"#;
+        let data = PointerActionItem::General(GeneralAction::Pause(PauseAction { duration: 1 }));
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_item_pointer() {
+        let json = r#"{"type":"pointerCancel"}"#;
+        let data = PointerActionItem::Pointer(PointerAction::Cancel);
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_item_invalid() {
+        let json = r#"{"type":"invalid"}"#;
+
+        assert!(serde_json::from_str::<PointerActionItem>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_parameters_mouse() {
+        let json = r#"{"pointerType":"mouse"}"#;
+        let data = PointerActionParameters {
+            pointer_type: PointerType::Mouse,
         };
 
-        let x = match body.find("x") {
-            Some(x) => {
-                Some(try_opt!(x.as_i64(),
-                              ErrorStatus::InvalidArgument,
-                              "Parameter 'x' was not an integer"))
-            },
-            None => None
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_parameters_pen() {
+        let json = r#"{"pointerType":"pen"}"#;
+        let data = PointerActionParameters {
+            pointer_type: PointerType::Pen,
         };
 
-        let y = match body.find("y") {
-            Some(y) => {
-                Some(try_opt!(y.as_i64(),
-                              ErrorStatus::InvalidArgument,
-                              "Parameter 'y' was not an integer"))
-            },
-            None => None
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_parameters_touch() {
+        let json = r#"{"pointerType":"touch"}"#;
+        let data = PointerActionParameters {
+            pointer_type: PointerType::Touch,
         };
 
-        Ok(PointerMoveAction {
-            duration: duration.into(),
-            origin: origin.into(),
-            x: x.into(),
-            y: y.into(),
-        })
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_item_invalid_type() {
+        let json = r#"{"type":"pointerInvalid"}"#;
+        assert!(serde_json::from_str::<PointerActionItem>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_with_subtype_missing() {
+        let json = r#"{"button":1}"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_with_subtype_invalid() {
+        let json = r#"{"type":"invalid"}"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_with_subtype_wrong() {
+        let json = r#"{"type":"pointerMove",button":1}"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_cancel() {
+        let json = r#"{"type":"pointerCancel"}"#;
+        let data = PointerAction::Cancel;
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_down() {
+        let json = r#"{"type":"pointerDown","button":1}"#;
+        let data = PointerAction::Down(PointerDownAction { button: 1 });
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_down_with_button_missing() {
+        let json = r#"{"type":"pointerDown"}"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_down_with_button_null() {
+        let json = r#"{
+            "type":"pointerDown",
+            "button":null
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_down_with_button_invalid_type() {
+        let json = r#"{
+            "type":"pointerDown",
+            "button":"foo",
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_down_with_button_negative() {
+        let json = r#"{
+            "type":"pointerDown",
+            "button":-30
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_move() {
+        let json = r#"{
+            "type":"pointerMove",
+            "duration":100,
+            "origin":"viewport",
+            "x":5,
+            "y":10
+        }"#;
+        let data = PointerAction::Move(PointerMoveAction {
+            duration: Some(100),
+            origin: PointerOrigin::Viewport,
+            x: Some(5),
+            y: Some(10),
+        });
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_missing_subtype() {
+        let json = r#"{
+            "duration":100,
+            "origin":"viewport",
+            "x":5,
+            "y":10
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_wrong_subtype() {
+        let json = r#"{
+            "type":"pointerUp",
+            "duration":100,
+            "origin":"viewport",
+            "x":5,
+            "y":10
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_with_duration_missing() {
+        let json = r#"{
+            "type":"pointerMove",
+            "origin":"viewport",
+            "x":5,
+            "y":10
+        }"#;
+        let data = PointerAction::Move(PointerMoveAction {
+            duration: None,
+            origin: PointerOrigin::Viewport,
+            x: Some(5),
+            y: Some(10),
+        });
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_with_duration_null() {
+        let json = r#"{
+            "type":"pointerMove",
+            "duration":null,
+            "origin":"viewport",
+            "x":5,
+            "y":10
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_with_duration_invalid_type() {
+        let json = r#"{
+            "type":"pointerMove",
+            "duration":"invalid",
+            "origin":"viewport",
+            "x":5,
+            "y":10
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_with_duration_negative() {
+        let json = r#"{
+            "type":"pointerMove",
+            "duration":-30,
+            "origin":"viewport",
+            "x":5,
+            "y":10
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_with_origin_missing() {
+        let json = r#"{
+            "type":"pointerMove",
+            "duration":100,
+            "x":5,
+            "y":10
+        }"#;
+        let data = PointerAction::Move(PointerMoveAction {
+            duration: Some(100),
+            origin: PointerOrigin::Viewport,
+            x: Some(5),
+            y: Some(10),
+        });
+
+        check_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_with_x_missing() {
+        let json = r#"{
+            "type":"pointerMove",
+            "duration":100,
+            "origin":"viewport",
+            "y":10
+        }"#;
+        let data = PointerAction::Move(PointerMoveAction {
+            duration: Some(100),
+            origin: PointerOrigin::Viewport,
+            x: None,
+            y: Some(10),
+        });
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_with_x_null() {
+        let json = r#"{
+            "type":"pointerMove",
+            "duration":100,
+            "origin":"viewport",
+            "x": null,
+            "y":10
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_with_x_invalid_type() {
+        let json = r#"{
+            "type":"pointerMove",
+            "duration":100,
+            "origin":"viewport",
+            "x": "invalid",
+            "y":10
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_with_y_missing() {
+        let json = r#"{
+            "type":"pointerMove",
+            "duration":100,
+            "origin":"viewport",
+            "x":5
+        }"#;
+        let data = PointerAction::Move(PointerMoveAction {
+            duration: Some(100),
+            origin: PointerOrigin::Viewport,
+            x: Some(5),
+            y: None,
+        });
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_with_y_null() {
+        let json = r#"{
+            "type":"pointerMove",
+            "duration":100,
+            "origin":"viewport",
+            "x":5,
+            "y":null
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_move_with_y_invalid_type() {
+        let json = r#"{
+            "type":"pointerMove",
+            "duration":100,
+            "origin":"viewport",
+            "x":5,
+            "y":"invalid"
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_up() {
+        let json = r#"{
+            "type":"pointerUp",
+            "button":1
+        }"#;
+        let data = PointerAction::Up(PointerUpAction { button: 1 });
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_action_up_with_button_missing() {
+        let json = r#"{
+            "type":"pointerUp"
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_up_with_button_null() {
+        let json = r#"{
+            "type":"pointerUp",
+            "button":null
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_up_with_button_invalid_type() {
+        let json = r#"{
+            "type":"pointerUp",
+            "button":"foo",
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_action_up_with_button_negative() {
+        let json = r#"{
+            "type":"pointerUp",
+            "button":-30
+        }"#;
+
+        assert!(serde_json::from_str::<PointerAction>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_origin_pointer() {
+        let json = r#""pointer""#;
+        let data = PointerOrigin::Pointer;
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_origin_viewport() {
+        let json = r#""viewport""#;
+        let data = PointerOrigin::Viewport;
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_origin_web_element() {
+        let json = r#"{"element-6066-11e4-a52e-4f735466cecf":"elem"}"#;
+        let data = PointerOrigin::Element(WebElement { id: "elem".into() });
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_origin_invalid_type() {
+        let data = r#""invalid""#;
+        assert!(serde_json::from_str::<PointerOrigin>(&data).is_err());
+    }
+
+    #[test]
+    fn test_json_pointer_type_mouse() {
+        let json = r#""mouse""#;
+        let data = PointerType::Mouse;
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_type_pen() {
+        let json = r#""pen""#;
+        let data = PointerType::Pen;
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_type_touch() {
+        let json = r#""touch""#;
+        let data = PointerType::Touch;
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_pointer_type_invalid_type() {
+        let json = r#""invalid""#;
+        assert!(serde_json::from_str::<PointerType>(&json).is_err());
     }
 }
-
-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 serde_json::{Map, Value};
+use std::convert::From;
 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,69 +40,86 @@ 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.
 pub trait CapabilitiesMatching {
     /// Match the BrowserCapabilities against some candidate capabilites
     ///
     /// Takes a BrowserCapabilites object and returns a set of capabilites that
     /// are valid for that browser, if any, or None if there are no matching
     /// capabilities.
-    fn match_browser<T: BrowserCapabilities>(&self, browser_capabilities: &mut T)
-                                             -> WebDriverResult<Option<Capabilities>>;
+    fn match_browser<T: BrowserCapabilities>(
+        &self,
+        browser_capabilities: &mut T,
+    ) -> WebDriverResult<Option<Capabilities>>;
 }
 
-#[derive(Debug, PartialEq)]
+#[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,150 +156,169 @@ 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) => {
-                match &**x {
-                    "normal" |
-                    "eager" |
-                    "none" => {},
-                    x => {
-                        return Err(WebDriverError::new(
-                            ErrorStatus::InvalidArgument,
-                            format!("Invalid page load strategy: {}", x)))
-                    }
+            &Value::String(ref x) => match &**x {
+                "normal" | "eager" | "none" => {}
+                x => {
+                    return Err(WebDriverError::new(
+                        ErrorStatus::InvalidArgument,
+                        format!("Invalid page load strategy: {}", x),
+                    ))
                 }
+            },
+            _ => {
+                return Err(WebDriverError::new(
+                    ErrorStatus::InvalidArgument,
+                    "pageLoadStrategy is not a string",
+                ))
             }
-            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
-                                                "pageLoadStrategy is not a string"))
         }
         Ok(())
     }
 
-    fn validate_proxy(proxy_value: &Json) -> WebDriverResult<()> {
-        let obj = try_opt!(proxy_value.as_object(),
-                           ErrorStatus::InvalidArgument,
-                           "proxy is not an object");
+    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() {
-                    Some("pac") |
-                    Some("direct") |
-                    Some("autodetect") |
-                    Some("system") |
-                    Some("manual") => {},
-                    Some(x) => return Err(WebDriverError::new(
-                        ErrorStatus::InvalidArgument,
-                        format!("Invalid proxyType value: {}", x))),
-                    None => return Err(WebDriverError::new(
-                        ErrorStatus::InvalidArgument,
-                        format!("proxyType is not a string: {}", value))),
+                "proxyType" => match value.as_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"
-                    ))
+                            format!("proxyAutoconfigUrl is not a valid URL: {}", x),
+                        )))?;
+                    }
+                    None => {
+                        return Err(WebDriverError::new(
+                            ErrorStatus::InvalidArgument,
+                            "proxyAutoconfigUrl is not a string",
+                        ))
+                    }
                 },
 
                 "ftpProxy" => SpecNewSessionParameters::validate_host(value, "ftpProxy")?,
                 "httpProxy" => SpecNewSessionParameters::validate_host(value, "httpProxy")?,
                 "noProxy" => SpecNewSessionParameters::validate_no_proxy(value)?,
                 "sslProxy" => SpecNewSessionParameters::validate_host(value, "sslProxy")?,
                 "socksProxy" => SpecNewSessionParameters::validate_host(value, "socksProxy")?,
                 "socksVersion" => if !value.is_number() {
                     return Err(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
-                        format!("socksVersion is not a number: {}", value)
-                    ))
+                        format!("socksVersion is not a number: {}", value),
+                    ));
                 },
 
-                x => return Err(WebDriverError::new(
-                    ErrorStatus::InvalidArgument,
-                    format!("Invalid proxy configuration entry: {}", x)))
+                x => {
+                    return Err(WebDriverError::new(
+                        ErrorStatus::InvalidArgument,
+                        format!("Invalid proxy configuration entry: {}", x),
+                    ))
+                }
             }
         }
 
         Ok(())
     }
 
-    fn validate_no_proxy(value: &Json) -> WebDriverResult<()> {
+    fn validate_no_proxy(value: &Value) -> WebDriverResult<()> {
         match value.as_array() {
             Some(hosts) => {
                 for host in hosts {
-                    match host.as_string() {
-                        Some(_) => {},
-                        None => return Err(WebDriverError::new(
-                            ErrorStatus::InvalidArgument,
-                            format!("noProxy item is not a string: {}", host)
-                        ))
+                    match host.as_str() {
+                        Some(_) => {}
+                        None => {
+                            return Err(WebDriverError::new(
+                                ErrorStatus::InvalidArgument,
+                                format!("noProxy item is not a string: {}", host),
+                            ))
+                        }
                     }
                 }
-            },
-            None => return Err(WebDriverError::new(
-                ErrorStatus::InvalidArgument,
-                format!("noProxy is not an array: {}", value)
-            ))
+            }
+            None => {
+                return Err(WebDriverError::new(
+                    ErrorStatus::InvalidArgument,
+                    format!("noProxy is not an array: {}", value),
+                ))
+            }
         }
 
         Ok(())
     }
 
     /// Validate whether a named capability is JSON value is a string containing a host
     /// and possible port
-    fn validate_host(value: &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)));
+                        format!("{} must not contain a scheme: {}", entry, host),
+                    ));
                 }
 
                 // Temporarily add a scheme so the host can be parsed as URL
                 let s = String::from(format!("http://{}", host));
                 let url = Url::parse(s.as_str()).or(Err(WebDriverError::new(
                     ErrorStatus::InvalidArgument,
-                    format!("{} is not a valid URL: {}", entry, host))))?;
+                    format!("{} is not a valid URL: {}", entry, host),
+                )))?;
 
-                if url.username() != "" ||
-                    url.password() != None ||
-                    url.path() != "/" ||
-                    url.query() != None ||
-                    url.fragment() != None {
-                        return Err(WebDriverError::new(
-                            ErrorStatus::InvalidArgument,
-                            format!("{} is not of the form host[:port]: {}", entry, host)));
-                    }
-            },
+                if url.username() != "" || url.password() != None || url.path() != "/"
+                    || url.query() != None || url.fragment() != None
+                {
+                    return Err(WebDriverError::new(
+                        ErrorStatus::InvalidArgument,
+                        format!("{} is not of the form host[:port]: {}", entry, host),
+                    ));
+                }
+            }
 
-            None => return Err(WebDriverError::new(
-                ErrorStatus::InvalidArgument,
-                format!("{} is not a string: {}", entry, value)
-            ))
+            None => {
+                return Err(WebDriverError::new(
+                    ErrorStatus::InvalidArgument,
+                    format!("{} is not a string: {}", entry, value),
+                ))
+            }
         }
 
         Ok(())
     }
 
-    fn validate_timeouts(value: &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,197 +343,134 @@ 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" |
-            "dismiss and notify" |
-            "ignore" => {},
+            "accept" | "accept and notify" | "dismiss" | "dismiss and notify" | "ignore" => {}
             x => {
                 return Err(WebDriverError::new(
                     ErrorStatus::InvalidArgument,
                     format!("Invalid unhandledPromptBehavior value: {}", x),
                 ))
             }
         }
 
         Ok(())
     }
 }
 
-impl Parameters for SpecNewSessionParameters {
-    fn from_json(body: &Json) -> WebDriverResult<SpecNewSessionParameters> {
-        let data = try_opt!(
-            body.as_object(),
-            ErrorStatus::UnknownError,
-            format!("Malformed capabilities, message body is not an object: {}", body)
-        );
-
-        let capabilities = try_opt!(
-            try_opt!(
-                data.get("capabilities"),
-                ErrorStatus::InvalidArgument,
-                "Malformed capabilities, missing \"capabilities\" field"
-            ).as_object(),
-            ErrorStatus::InvalidArgument,
-            "Malformed capabilities, \"capabilities\" field is not an object}"
-        );
-
-        let default_always_match = Json::Object(Capabilities::new());
-        let always_match = try_opt!(
-            capabilities
-                .get("alwaysMatch")
-                .unwrap_or(&default_always_match)
-                .as_object(),
-            ErrorStatus::InvalidArgument,
-            "Malformed capabilities, alwaysMatch field is not an object"
-        );
-        let default_first_matches = Json::Array(vec![]);
-        let first_matches = try_opt!(
-            capabilities
-                .get("firstMatch")
-                .unwrap_or(&default_first_matches)
-                .as_array(),
-            ErrorStatus::InvalidArgument,
-            "Malformed capabilities, firstMatch field is not an array"
-        ).iter()
-            .map(|x| {
-                x.as_object().map(|x| x.clone()).ok_or(WebDriverError::new(
-                    ErrorStatus::InvalidArgument,
-                    "Malformed capabilities, firstMatch entry is not an object",
-                ))
-            })
-            .collect::<WebDriverResult<Vec<Capabilities>>>()?;
-
-        return Ok(SpecNewSessionParameters {
-            alwaysMatch: always_match.clone(),
-            firstMatch: first_matches,
-        });
-    }
-}
-
-impl ToJson for SpecNewSessionParameters {
-    fn to_json(&self) -> Json {
-        let mut body = BTreeMap::new();
-        let mut capabilities = BTreeMap::new();
-        capabilities.insert("alwaysMatch".into(), self.alwaysMatch.to_json());
-        capabilities.insert("firstMatch".into(), self.firstMatch.to_json());
-        body.insert("capabilities".into(), capabilities.to_json());
-        Json::Object(body)
-    }
-}
-
 impl CapabilitiesMatching for SpecNewSessionParameters {
     fn match_browser<T: BrowserCapabilities>(
         &self,
         browser_capabilities: &mut T,
     ) -> WebDriverResult<Option<Capabilities>> {
-        let default = vec![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)) {
+                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))
-            })
+            .map(|merged| merged.and_then(|x| self.validate(x, browser_capabilities)))
             .collect::<WebDriverResult<Vec<Capabilities>>>()?;
 
         let selected = merged_capabilities
             .iter()
             .filter_map(|merged| {
                 browser_capabilities.init(merged);
 
                 for (key, value) in merged.iter() {
                     match &**key {
                         "browserName" => {
                             let browserValue = browser_capabilities
                                 .browser_name(merged)
                                 .ok()
                                 .and_then(|x| x);
 
-                            if value.as_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) &&
-                                !browser_capabilities
+                            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) &&
-                                !browser_capabilities
+                            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 +492,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 super::*;
+    use serde_json::{self, Value};
+    use test::check_deserialize;
+
+    fn validate_proxy(value: &str) -> WebDriverResult<()> {
+        let data = serde_json::from_str::<Value>(value).unwrap();
+        SpecNewSessionParameters::validate_proxy(&data)
+    }
+
+    #[test]
+    fn test_json_spec_new_session_parameters_alwaysMatch_only() {
+        let json = r#"{
+            "alwaysMatch":{}
+        }"#;
+        let data = SpecNewSessionParameters {
+            alwaysMatch: Capabilities::new(),
+            firstMatch: vec![Capabilities::new()],
+        };
+
+        check_deserialize(&json, &data);
+    }
 
-        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,17 @@
-use actions::{ActionSequence};
-use capabilities::{SpecNewSessionParameters, LegacyNewSessionParameters,
-                   CapabilitiesMatching, BrowserCapabilities, Capabilities};
-use common::{Date, Nullable, WebElement, FrameId, LocatorStrategy};
-use error::{WebDriverResult, WebDriverError, ErrorStatus};
-use httpapi::{Route, WebDriverExtensionRoute, VoidWebDriverExtensionRoute};
+use actions::ActionSequence;
+use capabilities::{BrowserCapabilities, Capabilities, CapabilitiesMatching,
+                   LegacyNewSessionParameters, SpecNewSessionParameters};
+use common::{Date, FrameId, LocatorStrategy, WebElement};
+use error::{ErrorStatus, WebDriverError, WebDriverResult};
+use httpapi::{Route, VoidWebDriverExtensionRoute, WebDriverExtensionRoute};
 use 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,1078 +59,1020 @@ pub enum WebDriverCommand<T: WebDriverEx
     ElementSendKeys(WebElement, SendKeysParameters),
     PerformActions(ActionsParameters),
     ReleaseActions,
     DismissAlert,
     AcceptAlert,
     GetAlertText,
     SendAlertText(SendKeysParameters),
     TakeScreenshot,
-    TakeElementScreenshot(WebElement),
+    TakeElementScreenshot(TakeScreenshotParameters),
     Status,
-    Extension(T)
+    Extension(T),
 }
 
-pub trait WebDriverExtensionCommand : Clone + Send + PartialEq {
-    fn parameters_json(&self) -> Option<Json>;
+pub trait WebDriverExtensionCommand: Clone + Send + PartialEq {
+    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 struct WebDriverMessage<U: WebDriverExtensionRoute = VoidWebDriverExtensionRoute> {
     pub session_id: Option<String>,
     pub command: WebDriverCommand<U::Command>,
 }
 
 impl<U: WebDriverExtensionRoute> WebDriverMessage<U> {
-    pub fn new(session_id: Option<String>,
-               command: WebDriverCommand<U::Command>)
-               -> WebDriverMessage<U> {
+    pub fn new(
+        session_id: Option<String>,
+        command: WebDriverCommand<U::Command>,
+    ) -> WebDriverMessage<U> {
         WebDriverMessage {
             session_id: session_id,
             command: command,
         }
     }
 
-    pub fn from_http(match_type: Route<U>,
-                     params: &Captures,
-                     raw_body: &str,
-                     requires_body: bool)
-                     -> WebDriverResult<WebDriverMessage<U>> {
+    pub fn from_http(
+        match_type: Route<U>,
+        params: &Captures,
+        raw_body: &str,
+        requires_body: bool,
+    ) -> WebDriverResult<WebDriverMessage<U>> {
         let session_id = WebDriverMessage::<U>::get_session_id(params);
-        let body_data = 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)
-            },
+            Route::NewSession => 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)
-            },
+            Route::Get => 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)
-            },
-            Route::GetWindowRect | Route::GetWindowPosition | Route::GetWindowSize => WebDriverCommand::GetWindowRect,
+            Route::SetTimeouts => 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)
-            },
-            Route::FindElements => {
-                let parameters: LocatorParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::FindElements(parameters)
-            },
+            Route::FindElement => WebDriverCommand::FindElement(serde_json::from_str(raw_body)?),
+            Route::FindElements => 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_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_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_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::IsDisplayed(element)
-            },
+            }
             Route::IsSelected => {
-                let element_id = try_opt!(params.name("elementId"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing elementId parameter");
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::IsSelected(element)
-            },
+            }
             Route::GetElementAttribute => {
-                let element_id = try_opt!(params.name("elementId"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing elementId parameter");
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
-                let attr = try_opt!(params.name("name"),
-                                    ErrorStatus::InvalidArgument,
-                                    "Missing name parameter").as_str();
+                let attr = try_opt!(
+                    params.name("name"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing name parameter"
+                ).as_str();
                 WebDriverCommand::GetElementAttribute(element, attr.into())
-            },
+            }
             Route::GetElementProperty => {
-                let element_id = try_opt!(params.name("elementId"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing elementId parameter");
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
-                let property = try_opt!(params.name("name"),
-                                        ErrorStatus::InvalidArgument,
-                                        "Missing name parameter").as_str();
+                let property = try_opt!(
+                    params.name("name"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing name parameter"
+                ).as_str();
                 WebDriverCommand::GetElementProperty(element, property.into())
-            },
+            }
             Route::GetCSSValue => {
-                let element_id = try_opt!(params.name("elementId"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing elementId parameter");
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
-                let property = try_opt!(params.name("propertyName"),
-                                        ErrorStatus::InvalidArgument,
-                                        "Missing propertyName parameter").as_str();
+                let property = try_opt!(
+                    params.name("propertyName"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing propertyName parameter"
+                ).as_str();
                 WebDriverCommand::GetCSSValue(element, property.into())
-            },
+            }
             Route::GetElementText => {
-                let element_id = try_opt!(params.name("elementId"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing elementId parameter");
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::GetElementText(element)
-            },
+            }
             Route::GetElementTagName => {
-                let element_id = try_opt!(params.name("elementId"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing elementId parameter");
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::GetElementTagName(element)
-            },
+            }
             Route::GetElementRect => {
-                let element_id = try_opt!(params.name("elementId"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing elementId parameter");
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::GetElementRect(element)
-            },
+            }
             Route::IsEnabled => {
-                let element_id = try_opt!(params.name("elementId"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing elementId parameter");
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::IsEnabled(element)
-            },
+            }
             Route::ElementClick => {
-                let element_id = try_opt!(params.name("elementId"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing elementId parameter");
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::ElementClick(element)
-            },
+            }
             Route::ElementTap => {
-                let element_id = try_opt!(params.name("elementId"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing elementId parameter");
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::ElementTap(element)
-            },
+            }
             Route::ElementClear => {
-                let element_id = try_opt!(params.name("elementId"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing elementId parameter");
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
                 WebDriverCommand::ElementClear(element)
-            },
+            }
             Route::ElementSendKeys => {
-                let element_id = try_opt!(params.name("elementId"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing elementId parameter");
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
                 let element = WebElement::new(element_id.as_str().into());
-                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)
-            },
-            Route::GetCookies => {
-                WebDriverCommand::GetCookies
-            },
+                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();
+                let name = try_opt!(
+                    params.name("name"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing 'name' parameter"
+                ).as_str()
+                    .into();
                 WebDriverCommand::GetNamedCookie(name)
-            },
-            Route::AddCookie => {
-                let parameters: AddCookieParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::AddCookie(parameters)
-            },
-            Route::DeleteCookies => {
-                WebDriverCommand::DeleteCookies
-            },
+            }
+            Route::AddCookie => WebDriverCommand::AddCookie(serde_json::from_str(raw_body)?),
+            Route::DeleteCookies => WebDriverCommand::DeleteCookies,
             Route::DeleteCookie => {
-                let name = try_opt!(params.name("name"),
-                                    ErrorStatus::InvalidArgument,
-                                    "Missing name parameter").as_str().into();
+                let name = try_opt!(
+                    params.name("name"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing name parameter"
+                ).as_str()
+                    .into();
                 WebDriverCommand::DeleteCookie(name)
-            },
+            }
             Route::PerformActions => {
-                let parameters: ActionsParameters = try!(Parameters::from_json(&body_data));
-                WebDriverCommand::PerformActions(parameters)
-            },
-            Route::ReleaseActions => {
-                WebDriverCommand::ReleaseActions
-            },
-            Route::DismissAlert => {
-                WebDriverCommand::DismissAlert
-            },
-            Route::AcceptAlert => {
-                WebDriverCommand::AcceptAlert
-            },
-            Route::GetAlertText => {
-                WebDriverCommand::GetAlertText
-            },
+                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)
-            },
+            Route::TakeElementScreenshot => {
+                WebDriverCommand::TakeElementScreenshot(serde_json::from_str(raw_body)?)
+            }
             Route::Status => WebDriverCommand::Status,
-            Route::Extension(ref extension) => {
-                try!(extension.command(params, &body_data))
-            }
+            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),
-                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)))
+            match serde_json::from_str(body) {
+                Ok(x @ Value::Object(_)) => Ok(x),
+                Ok(_) => Err(WebDriverError::new(
+                    ErrorStatus::InvalidArgument,
+                    "Body was not a JSON Object",
+                )),
+                Err(e) => {
+                    if e.is_io() {
+                        Err(WebDriverError::new(
+                            ErrorStatus::InvalidArgument,
+                            format!("I/O error whilst decoding body: {}", e),
+                        ))
+                    } else {
+                        let msg = format!("Failed to decode request as JSON: {}", body);
+                        let stack = format!("Syntax error at :{}:{}", e.line(), e.column());
+                        Err(WebDriverError::new_with_stack(
+                            ErrorStatus::InvalidArgument,
+                            msg,
+                            stack,
+                        ))
+                    }
                 }
             }
         } 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>> {
+    fn match_browser<T: BrowserCapabilities>(
+        &self,
+        browser_capabilities: &mut T,
+    ) -> WebDriverResult<Option<Capabilities>> {
         match self {
             &NewSessionParameters::Spec(ref x) => x.match_browser(browser_capabilities),
-            &NewSessionParameters::Legacy(ref x) => x.match_browser(browser_capabilities)
+            &NewSessionParameters::Legacy(ref x) => x.match_browser(browser_capabilities),
         }
     }
 }
 
-
-#[derive(Debug, PartialEq)]
-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"));
+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)
         }
-
-        let data = try_opt!(body.find("cookie").and_then(|x| x.as_object()),
-                            ErrorStatus::InvalidArgument,
-                            "Cookie parameter not found or not an object");
-
-        let name = try_opt!(
-            try_opt!(data.get("name"),
-                     ErrorStatus::InvalidArgument,
-                     "Missing 'name' parameter").as_string(),
-            ErrorStatus::InvalidArgument,
-            "'name' is not a string").to_string();
-
-        let value = try_opt!(
-            try_opt!(data.get("value"),
-                     ErrorStatus::InvalidArgument,
-                     "Missing 'value' parameter").as_string(),
-            ErrorStatus::InvalidArgument,
-            "'value' is not a string").to_string();
-
-        let path = match data.get("path") {
-            Some(path_json) => {
-                try!(Nullable::from_json(
-                    path_json,
-                    |x| {
-                        Ok(try_opt!(x.as_string(),
-                                    ErrorStatus::InvalidArgument,
-                                    "Failed to convert path to String").to_string())
-                    }))
-            },
-            None => Nullable::Null
-        };
-
-        let domain = match data.get("domain") {
-            Some(domain_json) => {
-                try!(Nullable::from_json(
-                    domain_json,
-                    |x| {
-                        Ok(try_opt!(x.as_string(),
-                                    ErrorStatus::InvalidArgument,
-                                    "Failed to convert domain to String").to_string())
-                    }))
-            },
-            None => Nullable::Null
-        };
-
-        let expiry = match data.get("expiry") {
-            Some(expiry_json) => {
-                try!(Nullable::from_json(
-                    expiry_json,
-                    |x| {
-                        Ok(Date::new(try_opt!(x.as_u64(),
-                                              ErrorStatus::InvalidArgument,
-                                              "Failed to convert expiry to Date")))
-                    }))
-            },
-            None => Nullable::Null
-        };
-
-        let secure = match data.get("secure") {
-            Some(x) => try_opt!(x.as_boolean(),
-                                ErrorStatus::InvalidArgument,
-                                "Failed to convert secure to boolean"),
-            None => false
-        };
-
-        let http_only = match data.get("httpOnly") {
-            Some(x) => try_opt!(x.as_boolean(),
-                                ErrorStatus::InvalidArgument,
-                                "Failed to convert httpOnly to boolean"),
-            None => false
-        };
+        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(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,
+    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)),
         };
-        let actual = Json::from_str(r#"{"x": 0, "y": null, "width": 2, "height": null}"#).unwrap();
-        assert_eq!(expected, Parameters::from_json(&actual).unwrap());
+
+        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::new("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,169 @@
-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)]
-pub struct Date(pub u64);
-
-impl Date {
-    pub fn new(timestamp: u64) -> Date {
-        Date(timestamp)
-    }
-}
-
-impl ToJson for Date {
-    fn to_json(&self) -> Json {
-        let &Date(x) = self;
-        x.to_json()
-    }
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub enum Nullable<T: ToJson> {
-    Value(T),
-    Null
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct 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>,
 }
 
-impl<T: ToJson> Nullable<T> {
-     pub fn is_null(&self) -> bool {
-        match *self {
-            Nullable::Value(_) => false,
-            Nullable::Null => true
-        }
-    }
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct Date(pub u64);
 
-     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
-        }
-    }
+#[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> {
-    //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
-        }
-    }
+// 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> Encodable for Nullable<T> {
-    fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
-        match *self {
-            Nullable::Value(ref x) => x.to_json().encode(s),
-            Nullable::Null => s.emit_option_none()
-        }
-    }
+#[derive(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> 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 {
-    pub id: String
+    #[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())
+        WebElement { id: id }
     }
 }
 
-#[derive(Debug, PartialEq)]
-pub enum FrameId {
-    Short(u16),
-    Element(WebElement),
-    Null
-}
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use serde_json;
+    use test::check_serialize_deserialize;
+
+    #[test]
+    fn test_json_date() {
+        let json = r#"1234"#;
+        let data = Date(1234);
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_date_invalid() {
+        let json = r#""2018-01-01""#;
+        assert!(serde_json::from_str::<Date>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_frame_id_short() {
+        let json = r#"1234"#;
+        let data = FrameId::Short(1234);
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_frame_id_webelement() {
+        let json = r#""elem""#;
+        let data = FrameId::Element(WebElement::new("elem".into()));
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_frame_id_invalid() {
+        let json = r#"true"#;
+        assert!(serde_json::from_str::<FrameId>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_locator_strategy_css_selector() {
+        let json = r#""css selector""#;
+        let data = LocatorStrategy::CSSSelector;
+
+        check_serialize_deserialize(&json, &data);
+    }
 
-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"))
-        }
+    #[test]
+    fn test_json_locator_strategy_link_text() {
+        let json = r#""link text""#;
+        let data = LocatorStrategy::LinkText;
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_locator_strategy_partial_link_text() {
+        let json = r#""partial link text""#;
+        let data = LocatorStrategy::PartialLinkText;
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_locator_strategy_tag_name() {
+        let json = r#""tag name""#;
+        let data = LocatorStrategy::TagName;
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_locator_strategy_xpath() {
+        let json = r#""xpath""#;
+        let data = LocatorStrategy::XPath;
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_locator_strategy_invalid() {
+        let json = r#""foo""#;
+        assert!(serde_json::from_str::<LocatorStrategy>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_webelement() {
+        let json = r#"{"element-6066-11e4-a52e-4f735466cecf":"elem"}"#;
+        let data = WebElement::new("elem".into());
+
+        check_serialize_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_webelement_invalid() {
+        let data = r#"{"elem-6066-11e4-a52e-4f735466cecf":"elem"}"#;
+        assert!(serde_json::from_str::<WebElement>(&data).is_err());
     }
 }
-
-impl ToJson for FrameId {
-    fn to_json(&self) -> Json {
-        match *self {
-            FrameId::Short(x) => {
-                Json::U64(x as u64)
-            },
-            FrameId::Element(ref x) => {
-                Json::String(x.id.clone())
-            },
-            FrameId::Null => {
-                Json::Null
-            }
-        }
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum LocatorStrategy {
-    CSSSelector,
-    LinkText,
-    PartialLinkText,
-    TagName,
-    XPath,
-}
-
-impl LocatorStrategy {
-    pub fn from_json(body: &Json) -> WebDriverResult<LocatorStrategy> {
-        match try_opt!(body.as_string(),
-                       ErrorStatus::InvalidArgument,
-                       "Expected locator strategy as string") {
-            "css selector" => Ok(LocatorStrategy::CSSSelector),
-            "link text" => Ok(LocatorStrategy::LinkText),
-            "partial link text" => Ok(LocatorStrategy::PartialLinkText),
-            "tag name" => Ok(LocatorStrategy::TagName),
-            "xpath" => Ok(LocatorStrategy::XPath),
-            x => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
-                                         format!("Unknown locator strategy {}", x)))
-        }
-    }
-}
-
-impl ToJson for LocatorStrategy {
-    fn to_json(&self) -> Json {
-        Json::String(match *self {
-            LocatorStrategy::CSSSelector => "css selector",
-            LocatorStrategy::LinkText => "link text",
-            LocatorStrategy::PartialLinkText => "partial link text",
-            LocatorStrategy::TagName => "tag name",
-            LocatorStrategy::XPath => "xpath"
-        }.to_string())
-    }
-}
--- a/testing/webdriver/src/error.rs
+++ b/testing/webdriver/src/error.rs
@@ -1,13 +1,13 @@
+use base64::DecodeError;
 use hyper::status::StatusCode;
-use rustc_serialize::base64::FromBase64Error;
-use rustc_serialize::json::{DecoderError, Json, ParserError, ToJson};
+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",
@@ -164,18 +173,17 @@ impl ErrorStatus {
             NoSuchWindow => "no such window",
             ScriptTimeout => "script timeout",
             SessionNotCreated => "session not created",
             StaleElementReference => "stale element reference",
             Timeout => "timeout",
             UnableToCaptureScreen => "unable to capture screen",
             UnableToSetCookie => "unable to set cookie",
             UnexpectedAlertOpen => "unexpected alert open",
-            UnknownCommand |
-            UnknownError => "unknown error",
+            UnknownCommand | UnknownError => "unknown error",
             UnknownMethod => "unknown method",
             UnknownPath => "unknown command",
             UnsupportedOperation => "unsupported operation",
         }
     }
 
     /// Returns the correct HTTP status code associated with the error type.
     pub fn http_status(&self) -> StatusCode {
@@ -248,71 +256,74 @@ 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>>
+    where
+        S: Into<Cow<'static, str>>,
     {
         WebDriverError {
             error: error,
             message: message.into(),
             stack: "".into(),
             delete_session: false,
         }
     }
 
     pub fn new_with_stack<S>(error: ErrorStatus, message: S, stack: S) -> WebDriverError
-        where S: Into<Cow<'static, str>>
+    where
+        S: Into<Cow<'static, str>>,
     {
         WebDriverError {
             error: error,
             message: message.into(),
             stack: stack.into(),
             delete_session: false,
         }
     }
 
     pub fn error_code(&self) -> &'static str {
         self.error.error_code()
     }
 
     pub fn http_status(&self) -> StatusCode {
         self.error.http_status()
     }
-
-    pub fn to_json_string(&self) -> String {
-        self.to_json().to_string()
-    }
-}
-
-impl ToJson for WebDriverError {
-    fn to_json(&self) -> Json {
-        let mut data = BTreeMap::new();
-        data.insert("error".into(), self.error_code().to_json());
-        data.insert("message".into(), self.message.to_json());
-        data.insert("stacktrace".into(), self.stack.to_json());
-
-        let mut wrapper = BTreeMap::new();
-        wrapper.insert("value".into(), Json::Object(data));
-        Json::Object(wrapper)
-    }
 }
 
 impl Error for WebDriverError {
     fn description(&self) -> &str {
         self.error_code()
     }
 
     fn cause(&self) -> Option<&Error> {
@@ -321,37 +332,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);
+    }
+}
--- 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,30 @@
 #![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,8 @@
 macro_rules! try_opt {
-    ($expr:expr, $err_type:expr, $err_msg:expr) => ({
+    ($expr:expr, $err_type:expr, $err_msg:expr) => {{
         match $expr {
             Some(x) => x,
-            None => return Err(WebDriverError::new($err_type, $err_msg))
+            None => return Err(WebDriverError::new($err_type, $err_msg)),
         }
-    })
+    }};
 }
--- a/testing/webdriver/src/response.rs
+++ b/testing/webdriver/src/response.rs
@@ -1,149 +1,112 @@
-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,
+        }
+
+        Wrapper { value: self }.serialize(serializer)
+    }
+}
+
+#[derive(Debug, PartialEq, Serialize)]
+pub struct CloseWindowResponse(pub Vec<String>);
+
+#[derive(Clone, Debug, PartialEq, Serialize)]
+pub struct CookieResponse(pub Cookie);
+
+#[derive(Debug, PartialEq, Serialize)]
+pub struct CookiesResponse(pub Vec<Cookie>);
 
-        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();
+#[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
+    /// pixels.
+    pub y: f64,
+
+    /// Height of the element’s [bounding rectangle] in CSS reference
+    /// pixels.
+    ///
+    /// [bounding rectangle]: https://drafts.fxtf.org/geometry/#rectangle
+    pub width: f64,
 
-        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
-            }
+    /// Width of the element’s [bounding rectangle] in CSS reference
+    /// pixels.
+    ///
+    /// [bounding rectangle]: https://drafts.fxtf.org/geometry/#rectangle
+    pub height: f64,
+}
+
+#[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, RustcEncodable)]
-pub struct CloseWindowResponse {
-    pub window_handles: Vec<String>,
-}
-
-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(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
-        }
-    }
-}
-
-#[derive(Debug, RustcEncodable)]
+#[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, RustcEncodable)]
-pub struct ValueResponse {
-    pub value: json::Json
-}
-
-impl ValueResponse {
-    pub fn new(value: json::Json) -> ValueResponse {
-        ValueResponse {
-            value: value
-        }
-    }
-}
-
-#[derive(Debug, RustcEncodable)]
-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,
+#[derive(Debug, PartialEq, Serialize)]
+pub struct ValueResponse(pub Value);
 
-    /// Y axis position of the top-left corner of the element relative
-    // to the current browsing context’s document element in CSS reference
-    // pixels.
-    pub y: f64,
-
-    /// Height of the element’s [bounding rectangle] in CSS reference
-    /// pixels.
-    ///
-    /// [bounding rectangle]: https://drafts.fxtf.org/geometry/#rectangle
-    pub width: f64,
-
-    /// Width of the element’s [bounding rectangle] in CSS reference
-    /// pixels.
-    ///
-    /// [bounding rectangle]: https://drafts.fxtf.org/geometry/#rectangle
-    pub height: f64,
-}
-
-#[derive(Debug)]
+#[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 +119,168 @@ 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 super::*;
+    use common::Date;
+    use serde_json;
+    use test::check_serialize;
+
+    #[test]
+    fn test_json_close_window_response() {
+        let json = r#"{"value":["1234"]}"#;
+        let data = WebDriverResponse::CloseWindow(CloseWindowResponse(vec!["1234".into()]));
+
+        check_serialize(&json, &data);
+    }
 
-    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_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 {
+            name: "foo".into(),
+            value: "bar".into(),
+            path: Some("/".into()),
+            domain: Some("foo.bar".into()),
+            expiry: Some(Date(123)),
+            secure: true,
+            httpOnly: false,
+        }));
+
+        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_without_optional() {
+        let json = r#"{"value":{
+            "name":"foo",
+            "value":"bar",
+            "path":"/",
+            "domain":null,
+            "secure":true,
+            "httpOnly":false
+        }}"#;
+        let data = WebDriverResponse::Cookie(CookieResponse(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 {
+    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(vec![Cookie {
             name: "name".into(),
             value: "value".into(),
-            path: Nullable::Value("/".into()),
-            domain: Nullable::Null,
-            expiry: Nullable::Null,
+            path: Some("/".into()),
+            domain: None,
+            expiry: None,
             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);
+        }]));
+
+        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_element_rect() {
-        let rect = ElementRectResponse {
+    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,
-        };
-        let resp = WebDriverResponse::ElementRect(rect);
-        let expected = r#"{"value": {"x": 0.0, "y": 1.0, "width": 2.0, "height": 3.0}}"#;
-        test(resp, expected);
+        });
+
+        check_serialize(&json, &data);
+    }
+
+    #[test]
+    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(Value::Object(value)));
+
+        check_serialize(&json, &data);
     }
 
     #[test]
-    fn test_window_rect() {
-        let rect = WindowRectResponse {
+    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_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_json_void_response() {
+        let json = r#"{"value":null}"#;
+        let data = WebDriverResponse::Void;
+
+        check_serialize(&json, &data);
+    }
+
+    #[test]
+    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,
-        };
-        let resp = WebDriverResponse::WindowRect(rect);
-        let expected = r#"{"value": {"x": 0, "y": 1, "width": 2, "height": 3}}"#;
-        test(resp, expected);
-    }
-
-    #[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);
-    }
+        });
 
-    #[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);
-    }
-
-    #[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);
+        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;
 
@@ -67,18 +68,18 @@ impl<T: WebDriverHandler<U>, U: WebDrive
                         Ok(_) => self.handler.handle_command(&self.session, msg),
                         Err(e) => Err(e),
                     };
 
                     match resp {
                         Ok(WebDriverResponse::NewSession(ref new_session)) => {
                             self.session = Some(Session::new(new_session.sessionId.clone()));
                         }
-                        Ok(WebDriverResponse::CloseWindow(CloseWindowResponse { ref window_handles })) => {
-                            if window_handles.len() == 0 {
+                        Ok(WebDriverResponse::CloseWindow(CloseWindowResponse(ref handles))) => {
+                            if handles.len() == 0 {
                                 debug!("Last window was closed, deleting session");
                                 self.delete_session();
                             }
                         }
                         Ok(WebDriverResponse::DeleteSession) => self.delete_session(),
                         Err(ref x) if x.delete_session => self.delete_session(),
                         _ => {}
                     }
@@ -205,24 +206,27 @@ 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(&response).unwrap()),
+                                    Err(err) => (err.http_status(),
+                                                 serde_json::to_string(&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(&err).unwrap()),
                 };
 
                 debug!("<- {} {}", status, resp_body);
 
                 {
                     let resp_status = res.status_mut();
                     *resp_status = status;
                 }
new file mode 100644
--- /dev/null
+++ b/testing/webdriver/src/test.rs
@@ -0,0 +1,43 @@
+use regex::Regex;
+use serde;
+use serde_json;
+use std;
+
+lazy_static! {
+    static ref MIN_REGEX: Regex = Regex::new(r"[\n\t]|\s{4}").unwrap();
+}
+
+pub fn check_serialize_deserialize<T>(json: &str, data: &T)
+where
+    T: std::fmt::Debug,
+    T: std::cmp::PartialEq,
+    T: serde::de::DeserializeOwned,
+    T: serde::Serialize,
+{
+    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,
+{
+    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,
+{
+    let min_json = MIN_REGEX.replace_all(json, "");
+
+    assert_eq!(min_json, serde_json::to_string(data).unwrap());
+}