diff --git a/.travis.yml b/.travis.yml index e0d5eb5..c96f8af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,4 +14,5 @@ script: - cargo test --features trace - cargo test --features functions - cargo test --features chrono - - cargo test --features "backup blob chrono functions load_extension trace" + - cargo test --features serde_json + - cargo test --features "backup blob chrono functions load_extension serde_json trace" diff --git a/Cargo.toml b/Cargo.toml index 7e83a17..8fb06d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ bitflags = "~0.1" libc = "~0.2" clippy = {version = "~0.0.58", optional = true} chrono = { version = "~0.2", optional = true } +serde_json = { version = "0.6", optional = true } [dev-dependencies] tempdir = "~0.3.4" diff --git a/Changelog.md b/Changelog.md index e000549..9e6729a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # Version UPCOMING (...) +* Adds support for serializing types from the `serde_json` crate. Requires the `serde_json` feature. * Adds support for serializing types from the `chrono` crate. Requires the `chrono` feature. * Removes `load_extension` feature from `libsqlite3-sys`. `load_extension` is still available on rusqlite itself. diff --git a/appveyor.yml b/appveyor.yml index 0d9a5f3..9b0258c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,7 +16,7 @@ build: false test_script: - cargo test --lib --verbose - - cargo test --lib --features "backup blob chrono functions load_extension trace" + - cargo test --lib --features "backup blob chrono functions load_extension serde_json trace" cache: - C:\Users\appveyor\.cargo diff --git a/src/types/mod.rs b/src/types/mod.rs index 46a5c54..841242b 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -67,6 +67,8 @@ pub use ffi::{SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NUL mod time; #[cfg(feature = "chrono")] mod chrono; +#[cfg(feature = "serde_json")] +mod serde_json; /// A trait for types that can be converted into SQLite values. pub trait ToSql { diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs new file mode 100644 index 0000000..dc1eeff --- /dev/null +++ b/src/types/serde_json.rs @@ -0,0 +1,66 @@ +//! `ToSql` and `FromSql` implementation for JSON `Value`. +extern crate serde_json; + +use libc::c_int; +use self::serde_json::Value; + +use {Error, Result}; +use types::{FromSql, ToSql}; + +use ffi; +use ffi::sqlite3_stmt; +use ffi::sqlite3_column_type; + +/// Serialize JSON `Value` to text. +impl ToSql for Value { + unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { + let s = serde_json::to_string(self).unwrap(); + s.bind_parameter(stmt, col) + } +} + +/// Deserialize text/blob to JSON `Value`. +impl FromSql for Value { + unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { + let value_result = match sqlite3_column_type(stmt, col) { + ffi::SQLITE_TEXT => { + let s = try!(String::column_result(stmt, col)); + serde_json::from_str(&s) + } + ffi::SQLITE_BLOB => { + let blob = try!(Vec::::column_result(stmt, col)); + serde_json::from_slice(&blob) + } + _ => return Err(Error::InvalidColumnType), + }; + value_result.map_err(|err| Error::FromSqlConversionFailure(Box::new(err))) + } +} + +#[cfg(test)] +mod test { + use Connection; + use super::serde_json; + + fn checked_memory_handle() -> Connection { + let db = Connection::open_in_memory().unwrap(); + db.execute_batch("CREATE TABLE foo (t TEXT, b BLOB)").unwrap(); + db + } + + #[test] + fn test_json_value() { + let db = checked_memory_handle(); + + let json = r#"{"foo": 13, "bar": "baz"}"#; + let data: serde_json::Value = serde_json::from_str(json).unwrap(); + db.execute("INSERT INTO foo (t, b) VALUES (?, ?)", + &[&data, &json.as_bytes()]) + .unwrap(); + + let t: serde_json::Value = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); + assert_eq!(data, t); + let b: serde_json::Value = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap(); + assert_eq!(data, b); + } +}