Skip to content

RUST-2240 Move serde_json API behind a feature flag #567

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ serde_path_to_error = ["dep:serde_path_to_error"]
# should be used in conjunction with chrono-0_4 or uuid-0_8.
serde_with-3 = ["dep:serde_with", "dep:serde"]
serde = ["dep:serde"]
serde_json-1 = ["dep:serde_json"]

[lib]
name = "bson"
Expand All @@ -57,7 +58,7 @@ ahash = "0.8.0"
chrono = { version = "0.4.15", features = ["std"], default-features = false, optional = true }
rand = "0.9"
serde = { version = "1.0", features = ["derive"], optional = true }
serde_json = { version = "1.0", features = ["preserve_order"] }
serde_json = { version = "1.0", features = ["preserve_order"], optional = true }
indexmap = "2.1.0"
hex = "0.4.2"
base64 = "0.22.1"
Expand All @@ -84,6 +85,7 @@ pretty_assertions = "0.6.1"
proptest = "1.0.0"
serde_bytes = "0.11"
serde_path_to_error = "0.1.16"
serde_json = "1"
chrono = { version = "0.4", features = ["serde", "clock", "std"], default-features = false }

[package.metadata.docs.rs]
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ Note that if you are using `bson` through the `mongodb` crate, you do not need t
| `chrono-0_4` | Enable support for v0.4 of the [`chrono`](https://docs.rs/chrono/0.4) crate in the public API. | n/a | no |
| `uuid-1` | Enable support for v1.x of the [`uuid`](https://docs.rs/uuid/1.0) crate in the public API. | n/a | no |
| `time-0_3` | Enable support for v0.3 of the [`time`](https://docs.rs/time/0.3) crate in the public API. | n/a | no |
| `serde_with-3` | Enable [`serde_with`](https://docs.rs/serde_with/3.x) 3.x integrations for `bson::DateTime` and `bson::Uuid`.| serde_with | no |
| `serde_path_to_error` | Enable support for error paths via integration with [`serde_path_to_error`](https://docs.rs/serde_path_to_err/latest). This is an unstable feature and any breaking changes to `serde_path_to_error` may affect usage of it via this feature. | serde_path_to_error | no |
| `serde_with-3` | Enable [`serde_with`](https://docs.rs/serde_with/3.x) 3.x integrations for `bson::DateTime` and `bson::Uuid`.| `serde_with` | no |
| `serde_path_to_error` | Enable support for error paths via integration with [`serde_path_to_error`](https://docs.rs/serde_path_to_err/latest). This is an unstable feature and any breaking changes to `serde_path_to_error` may affect usage of it via this feature. | `serde_path_to_error` | no |
| `compat-3-0-0` | Required for future compatibility if default features are disabled. | n/a | no |
| `large_dates` | Increase the supported year range for some `bson::DateTime` utilities from +/-9,999 (inclusive) to +/-999,999 (inclusive). Note that enabling this feature can impact performance and introduce parsing ambiguities. | n/a | no |
| `serde_json-1` | Enable support for v1.x of the [`serde_json`](https://docs.rs/serde_json/1.x) crate in the public API. | `serde_json` | no |

## Overview of the BSON Format

Expand Down
2 changes: 1 addition & 1 deletion serde-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ edition = "2018"
default = []

[dependencies]
bson = { path = "..", features = ["uuid-1", "chrono-0_4", "serde", "serde_with-3"] }
bson = { path = "..", features = ["uuid-1", "chrono-0_4", "serde", "serde_with-3", "serde_json-1"] }
serde = { version = "1.0", features = ["derive"] }
pretty_assertions = "0.6.1"
hex = "0.4.2"
Expand Down
142 changes: 2 additions & 140 deletions src/bson.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ use std::{
ops::Index,
};

use serde_json::{json, Value};

pub use crate::document::Document;
use crate::{base64, oid, raw::CString, spec::ElementType, Binary, Decimal128};
use crate::{oid, raw::CString, spec::ElementType, Binary, Decimal128};

/// Possible BSON value types.
#[derive(Clone, Default, PartialEq)]
Expand Down Expand Up @@ -450,143 +448,7 @@ where
}
}

/// This will create the [relaxed Extended JSON v2](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/) representation of the provided [`Bson`](../enum.Bson.html).
impl From<Bson> for Value {
fn from(bson: Bson) -> Self {
bson.into_relaxed_extjson()
}
}

impl Bson {
/// Converts the Bson value into its [relaxed extended JSON representation](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/).
pub fn into_relaxed_extjson(self) -> Value {
match self {
Bson::Double(v) if v.is_nan() => {
let s = if v.is_sign_negative() { "-NaN" } else { "NaN" };

json!({ "$numberDouble": s })
}
Bson::Double(v) if v.is_infinite() => {
let s = if v.is_sign_negative() {
"-Infinity"
} else {
"Infinity"
};

json!({ "$numberDouble": s })
}
Bson::Double(v) => json!(v),
Bson::String(v) => json!(v),
Bson::Array(v) => Value::Array(v.into_iter().map(Bson::into_relaxed_extjson).collect()),
Bson::Document(v) => Value::Object(
v.into_iter()
.map(|(k, v)| (k, v.into_relaxed_extjson()))
.collect(),
),
Bson::Boolean(v) => json!(v),
Bson::Null => Value::Null,
Bson::RegularExpression(Regex { pattern, options }) => {
let mut chars: Vec<_> = options.as_str().chars().collect();
chars.sort_unstable();

let options: String = chars.into_iter().collect();

json!({
"$regularExpression": {
"pattern": pattern.into_string(),
"options": options,
}
})
}
Bson::JavaScriptCode(code) => json!({ "$code": code }),
Bson::JavaScriptCodeWithScope(JavaScriptCodeWithScope { code, scope }) => json!({
"$code": code,
"$scope": Bson::Document(scope).into_relaxed_extjson(),
}),
Bson::Int32(v) => v.into(),
Bson::Int64(v) => v.into(),
Bson::Timestamp(Timestamp { time, increment }) => json!({
"$timestamp": {
"t": time,
"i": increment,
}
}),
Bson::Binary(Binary { subtype, ref bytes }) => {
let tval: u8 = From::from(subtype);
json!({
"$binary": {
"base64": base64::encode(bytes),
"subType": hex::encode([tval]),
}
})
}
Bson::ObjectId(v) => json!({"$oid": v.to_hex()}),
Bson::DateTime(v) if v.timestamp_millis() >= 0 && v.to_time_0_3().year() <= 9999 => {
json!({
// Unwrap safety: timestamps in the guarded range can always be formatted.
"$date": v.try_to_rfc3339_string().unwrap(),
})
}
Bson::DateTime(v) => json!({
"$date": { "$numberLong": v.timestamp_millis().to_string() },
}),
Bson::Symbol(v) => json!({ "$symbol": v }),
Bson::Decimal128(v) => json!({ "$numberDecimal": v.to_string() }),
Bson::Undefined => json!({ "$undefined": true }),
Bson::MinKey => json!({ "$minKey": 1 }),
Bson::MaxKey => json!({ "$maxKey": 1 }),
Bson::DbPointer(DbPointer {
ref namespace,
ref id,
}) => json!({
"$dbPointer": {
"$ref": namespace,
"$id": {
"$oid": id.to_hex()
}
}
}),
}
}

/// Converts the Bson value into its [canonical extended JSON representation](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/).
pub fn into_canonical_extjson(self) -> Value {
match self {
Bson::Int32(i) => json!({ "$numberInt": i.to_string() }),
Bson::Int64(i) => json!({ "$numberLong": i.to_string() }),
Bson::Double(f) if f.is_normal() => {
let mut s = f.to_string();
if f.fract() == 0.0 {
s.push_str(".0");
}

json!({ "$numberDouble": s })
}
Bson::Double(f) if f == 0.0 => {
let s = if f.is_sign_negative() { "-0.0" } else { "0.0" };

json!({ "$numberDouble": s })
}
Bson::DateTime(date) => {
json!({ "$date": { "$numberLong": date.timestamp_millis().to_string() } })
}
Bson::Array(arr) => {
Value::Array(arr.into_iter().map(Bson::into_canonical_extjson).collect())
}
Bson::Document(arr) => Value::Object(
arr.into_iter()
.map(|(k, v)| (k, v.into_canonical_extjson()))
.collect(),
),
Bson::JavaScriptCodeWithScope(JavaScriptCodeWithScope { code, scope }) => json!({
"$code": code,
"$scope": Bson::Document(scope).into_canonical_extjson(),
}),

other => other.into_relaxed_extjson(),
}
}

/// Get the [`ElementType`] of this value.
pub fn element_type(&self) -> ElementType {
match *self {
Expand Down Expand Up @@ -668,7 +530,7 @@ impl Bson {
} else {
doc! {
"$binary": {
"base64": base64::encode(bytes),
"base64": crate::base64::encode(bytes),
"subType": hex::encode([tval]),
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Contains the error-related types for the `bson` crate.

mod decimal128;
mod oid;
mod uuid;
Expand Down
93 changes: 2 additions & 91 deletions src/extjson.rs
Original file line number Diff line number Diff line change
@@ -1,92 +1,3 @@
//! Deserialization and serialization of [MongoDB Extended JSON v2](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/)
//!
//! ## Overview of Extended JSON
//!
//! MongoDB Extended JSON (abbreviated extJSON) is format of JSON that allows for the encoding of
//! BSON type information. Normal JSON cannot unambiguously represent all BSON types losslessly, so
//! an extension was designed to include conventions for representing those types.
//!
//! For example, a BSON binary is represented by the following format:
//! ```text
//! {
//! "$binary": {
//! "base64": <base64 encoded payload as a string>,
//! "subType": <subtype as a one or two character hex string>,
//! }
//! }
//! ```
//! For more information on extJSON and the complete list of translations, see the [official MongoDB documentation](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/).
//!
//! All MongoDB drivers and BSON libraries interpret and produce extJSON, so it can serve as a
//! useful tool for communicating between applications where raw BSON bytes cannot be used (e.g. via
//! JSON REST APIs). It's also useful for representing BSON data as a string.
//!
//! ### Canonical and Relaxed Modes
//!
//! There are two modes of extJSON: "Canonical" and "Relaxed". They are the same except for the
//! following differences:
//! - In relaxed mode, all BSON numbers are represented by the JSON number type, rather than the
//! object notation.
//! - In relaxed mode, the string in the datetime object notation is RFC 3339 (ISO-8601) formatted
//! (if the date is after 1970).
//!
//! e.g.
//! ```rust
//! # use bson::bson;
//! let doc = bson!({ "x": 5, "d": bson::DateTime::now() });
//!
//! println!("relaxed: {}", doc.clone().into_relaxed_extjson());
//! // relaxed: "{"x":5,"d":{"$date":"2020-06-01T22:19:13.075Z"}}"
//!
//! println!("canonical: {}", doc.into_canonical_extjson());
//! // canonical: {"x":{"$numberInt":"5"},"d":{"$date":{"$numberLong":"1591050020711"}}}
//! ```
//!
//! Canonical mode is useful when BSON values need to be round tripped without losing any type
//! information. Relaxed mode is more useful when debugging or logging BSON data.
//!
//! ## Deserializing Extended JSON
//!
//! Extended JSON can be deserialized using [`Bson`](../enum.Bson.html)'s
//! `TryFrom<serde_json::Value>` implementation. This implementation accepts both canonical and
//! relaxed extJSON, and the two modes can even be mixed within a single representation.
//!
//! e.g.
//! ```rust
//! # use bson::Bson;
//! # use serde_json::json;
//! # use std::convert::{TryFrom, TryInto};
//! let json_doc = json!({ "x": 5i32, "y": { "$numberInt": "5" }, "z": { "subdoc": "hello" } });
//! let bson: Bson = json_doc.try_into().unwrap(); // Bson::Document(...)
//!
//! let json_date = json!({ "$date": { "$numberLong": "1590972160292" } });
//! let bson_date: Bson = json_date.try_into().unwrap(); // Bson::DateTime(...)
//!
//! let invalid_ext_json = json!({ "$numberLong": 5 });
//! Bson::try_from(invalid_ext_json).expect_err("5 should be a string");
//! ```
//!
//! ## Serializing to Extended JSON
//!
//! Extended JSON can be created via [`Bson`](../enum.Bson.html)'s `Into<serde_json::Value>`
//! implementation (which will create relaxed extJSON),
//! [`Bson::into_relaxed_extjson`](../enum.Bson.html#method.into_relaxed_extjson), and
//! [`Bson::into_canonical_extjson`](../enum.Bson.html#method.into_canonical_extjson).
//!
//! e.g.
//! ```rust
//! # use bson::{bson, oid};
//! let doc = bson!({ "x": 5i32, "_id": oid::ObjectId::new() });
//!
//! let relaxed_extjson: serde_json::Value = doc.clone().into();
//! println!("{}", relaxed_extjson); // { "x": 5, "_id": { "$oid": <hexstring> } }
//!
//! let relaxed_extjson = doc.clone().into_relaxed_extjson();
//! println!("{}", relaxed_extjson); // { "x": 5, "_id": { "$oid": <hexstring> } }
//!
//! let canonical_extjson = doc.into_canonical_extjson();
//! println!("{}", canonical_extjson); // { "x": { "$numberInt": "5" }, "_id": { "$oid": <hexstring> } }
//! ```

pub mod de;
#[cfg(feature = "serde_json-1")]
pub(crate) mod json;
pub(crate) mod models;
Loading