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 487712 b61ce753f01e0c1a2ae06c58058880e653a857f0
parent 487711 7ef3912feb2c844edabbdb6376838d046432bc22
child 487713 7c97853a85b94dc65cfda23a8ee6f826a606505d
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersato
bugs1396821
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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());
+}