Skip to content

Commit 3b2b3c5

Browse files
authored
Merge pull request #1331 from WonderLawrence/rawvalue-from-string-unchecked
Add RawValue::from_string_unchecked
2 parents 827a315 + 0406d96 commit 3b2b3c5

2 files changed

Lines changed: 93 additions & 0 deletions

File tree

src/raw.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,49 @@ impl RawValue {
191191
Ok(Self::from_owned(json.into_boxed_str()))
192192
}
193193

194+
/// Convert an owned `String` of JSON data to an owned `RawValue` without
195+
/// checking that it contains valid JSON.
196+
///
197+
/// This is the unchecked counterpart of [`RawValue::from_string`], for
198+
/// strings that are already known to be valid JSON, such as the output of
199+
/// another JSON serializer. Unlike `from_string`, it does not re-parse the
200+
/// string; the only cost is `String::into_boxed_str`.
201+
///
202+
/// # Safety
203+
///
204+
/// The string passed in must contain a single well-formed JSON value with
205+
/// no leading or trailing whitespace. `RawValue` is written verbatim into
206+
/// JSON output wherever it is embedded, and other code (including unsafe
207+
/// code) is allowed to rely on `RawValue` upholding this invariant, in the
208+
/// same way that unsafe code may rely on `str` containing valid UTF-8.
209+
///
210+
/// In debug builds this contract is checked with a `debug_assert!` that
211+
/// re-parses the input; the check is compiled out in release builds, so
212+
/// only release performance matches the "no re-parse" guarantee above.
213+
///
214+
/// # Example
215+
///
216+
/// ```
217+
/// use serde_json::value::RawValue;
218+
///
219+
/// let json = serde_json::to_string(&[1, 2, 3])?;
220+
///
221+
/// // SAFETY: `json` was produced by serde_json's own serializer, so it is
222+
/// // a single well-formed JSON value without surrounding whitespace.
223+
/// let raw = unsafe { RawValue::from_string_unchecked(json) };
224+
///
225+
/// assert_eq!(raw.get(), "[1,2,3]");
226+
/// # Ok::<(), serde_json::Error>(())
227+
/// ```
228+
pub unsafe fn from_string_unchecked(json: String) -> Box<Self> {
229+
debug_assert!(
230+
crate::from_str::<&Self>(&json).is_ok_and(|v| v.json.len() == json.len()),
231+
"from_string_unchecked: input is not a single well-formed JSON value \
232+
with no leading or trailing whitespace",
233+
);
234+
Self::from_owned(json.into_boxed_str())
235+
}
236+
194237
/// Access the JSON text underlying a raw value.
195238
///
196239
/// # Example

tests/test.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2455,6 +2455,56 @@ fn test_boxed_raw_value() {
24552455
assert_eq!(r#"["a",42,{"foo": "bar"},null]"#, array_to_string);
24562456
}
24572457

2458+
#[cfg(feature = "raw_value")]
2459+
#[test]
2460+
fn test_raw_value_from_string_unchecked() {
2461+
#[derive(Serialize)]
2462+
struct Wrapper {
2463+
a: i8,
2464+
b: Box<RawValue>,
2465+
c: i8,
2466+
}
2467+
2468+
let json = r#"{"foo":[1,2,3],"bar":"\"escaped\""}"#.to_owned();
2469+
let checked = RawValue::from_string(json.clone()).unwrap();
2470+
let unchecked = unsafe { RawValue::from_string_unchecked(json) };
2471+
assert_eq!(checked.get(), unchecked.get());
2472+
2473+
let wrapper = Wrapper {
2474+
a: 1,
2475+
b: unchecked,
2476+
c: 3,
2477+
};
2478+
let wrapper_to_string = serde_json::to_string(&wrapper).unwrap();
2479+
assert_eq!(
2480+
r#"{"a":1,"b":{"foo":[1,2,3],"bar":"\"escaped\""},"c":3}"#,
2481+
wrapper_to_string,
2482+
);
2483+
2484+
// A string with excess capacity is preserved as-is.
2485+
let mut spare = String::with_capacity(64);
2486+
spare.push_str("[1,2,3]");
2487+
let raw = unsafe { RawValue::from_string_unchecked(spare) };
2488+
assert_eq!("[1,2,3]", raw.get());
2489+
}
2490+
2491+
// The debug_assert! inside from_string_unchecked is compiled out of release
2492+
// builds, so #[should_panic] can only be exercised under debug (which is the
2493+
// default `cargo test` profile).
2494+
#[cfg(all(feature = "raw_value", debug_assertions))]
2495+
#[test]
2496+
#[should_panic = "from_string_unchecked"]
2497+
fn test_raw_value_from_string_unchecked_debug_asserts_whitespace() {
2498+
let _ = unsafe { RawValue::from_string_unchecked(" 42".to_owned()) };
2499+
}
2500+
2501+
#[cfg(all(feature = "raw_value", debug_assertions))]
2502+
#[test]
2503+
#[should_panic = "from_string_unchecked"]
2504+
fn test_raw_value_from_string_unchecked_debug_asserts_well_formed() {
2505+
let _ = unsafe { RawValue::from_string_unchecked("[1,".to_owned()) };
2506+
}
2507+
24582508
#[cfg(feature = "raw_value")]
24592509
#[test]
24602510
fn test_raw_invalid_utf8() {

0 commit comments

Comments
 (0)