Skip to content

Commit 915b254

Browse files
authored
Merge pull request #39 from Diggsey/feature/issue-38-conversions
Add FromIterator and serde_json conversions for IValue (#38)
2 parents 898cffe + 45410e5 commit 915b254

4 files changed

Lines changed: 154 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 0.1.7
4+
5+
- Add `FromIterator<T: Into<IValue>>` for `IValue` (collects into an array) and `FromIterator<(K: Into<IString>, V: Into<IValue>)>` for `IValue` (collects into an object), mirroring `serde_json::Value`.
6+
- Add `From<serde_json::Value> for IValue` and `From<IValue> for serde_json::Value` for smoother interoperability with `serde_json`.
7+
38
## 0.1.6
49

510
- **Breaking:** Remove `Borrow<str>` impl for `IString` by default. The impl violates the `Borrow` contract because `IString` hashes by pointer, not by contents, causing silent lookup failures in `HashMap`/`HashSet` when using `&str` keys. A `broken-borrow-impl-compat` feature flag is available as a temporary compatibility measure.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ijson"
3-
version = "0.1.6"
3+
version = "0.1.7"
44
authors = ["Diggory Blake <diggsey@googlemail.com>"]
55
edition = "2018"
66
readme = "README.md"

src/number.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,52 @@ impl TryFrom<f32> for INumber {
670670
}
671671
}
672672

673+
/// Converts a [`serde_json::Number`] into an [`INumber`].
674+
///
675+
/// Conversion may be lossy if the number is not exactly representable as an
676+
/// `INumber`. The exact behaviour in that case (e.g. clamping an out-of-range
677+
/// magnitude) is not guaranteed to be stable across versions.
678+
impl From<serde_json::Number> for INumber {
679+
fn from(n: serde_json::Number) -> Self {
680+
if let Some(v) = n.as_u64() {
681+
INumber::from(v)
682+
} else if let Some(v) = n.as_i64() {
683+
INumber::from(v)
684+
} else {
685+
// A serde_json number is always representable as an f64, so this
686+
// cannot return `None`; if it does, an invariant broke.
687+
let v = n
688+
.as_f64()
689+
.expect("a serde_json number is always an integer or float");
690+
// Standard JSON numbers are finite. Only the `arbitrary_precision`
691+
// feature can parse a magnitude beyond f64's range (an infinity);
692+
// clamp it so the result stays a finite, representable number and
693+
// `try_from` cannot fail.
694+
INumber::try_from(v.clamp(f64::MIN, f64::MAX)).expect("a clamped f64 is always finite")
695+
}
696+
}
697+
}
698+
699+
/// Converts an [`INumber`] into a [`serde_json::Number`].
700+
///
701+
/// Conversion may be lossy if the number is not exactly representable as a
702+
/// `serde_json::Number`. The exact behaviour in that case (e.g. rounding) is
703+
/// not guaranteed to be stable across versions.
704+
impl From<INumber> for serde_json::Number {
705+
fn from(n: INumber) -> Self {
706+
if let Some(v) = n.to_u64() {
707+
serde_json::Number::from(v)
708+
} else if let Some(v) = n.to_i64() {
709+
serde_json::Number::from(v)
710+
} else {
711+
// Not an integer, so it is stored as an f64. An `INumber` is always
712+
// finite, so `from_f64` cannot fail; a failure here would mean the
713+
// `INumber` invariant was violated.
714+
serde_json::Number::from_f64(n.to_f64_lossy()).expect("an INumber is always finite")
715+
}
716+
}
717+
}
718+
673719
impl PartialEq for INumber {
674720
fn eq(&self, other: &Self) -> bool {
675721
self.cmp(other) == Ordering::Equal

src/value.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::convert::TryFrom;
44
use std::fmt::{self, Debug, Formatter};
55
use std::hash::Hash;
66
use std::hint::unreachable_unchecked;
7+
use std::iter::FromIterator;
78
use std::mem;
89
use std::ops::{Deref, Index, IndexMut};
910
use std::ptr::NonNull;
@@ -973,6 +974,64 @@ impl From<f64> for IValue {
973974
}
974975
}
975976

977+
/// Collects an iterator of values into an array [`IValue`].
978+
impl<T: Into<IValue>> FromIterator<T> for IValue {
979+
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
980+
IArray::from_iter(iter).into()
981+
}
982+
}
983+
984+
/// Collects an iterator of key-value pairs into an object [`IValue`].
985+
impl<K: Into<IString>, V: Into<IValue>> FromIterator<(K, V)> for IValue {
986+
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
987+
IObject::from_iter(iter).into()
988+
}
989+
}
990+
991+
/// Converts a [`serde_json::Value`] into an [`IValue`].
992+
///
993+
/// Conversion of numeric values may be lossy if the number is not exactly
994+
/// representable in the destination type. The exact behaviour in that case
995+
/// (e.g. rounding, or clamping an out-of-range magnitude) is not guaranteed
996+
/// to be stable across versions.
997+
impl From<serde_json::Value> for IValue {
998+
fn from(other: serde_json::Value) -> Self {
999+
match other {
1000+
serde_json::Value::Null => IValue::NULL,
1001+
serde_json::Value::Bool(b) => b.into(),
1002+
serde_json::Value::Number(n) => INumber::from(n).into(),
1003+
serde_json::Value::String(s) => s.into(),
1004+
serde_json::Value::Array(a) => a.into_iter().collect(),
1005+
serde_json::Value::Object(o) => o.into_iter().collect(),
1006+
}
1007+
}
1008+
}
1009+
1010+
/// Converts an [`IValue`] into a [`serde_json::Value`].
1011+
///
1012+
/// Conversion of numeric values may be lossy if the number is not exactly
1013+
/// representable in the destination type. The exact behaviour in that case
1014+
/// (e.g. rounding, or clamping an out-of-range magnitude) is not guaranteed
1015+
/// to be stable across versions.
1016+
impl From<IValue> for serde_json::Value {
1017+
fn from(other: IValue) -> Self {
1018+
match other.destructure() {
1019+
Destructured::Null => serde_json::Value::Null,
1020+
Destructured::Bool(b) => serde_json::Value::Bool(b),
1021+
Destructured::Number(n) => serde_json::Value::Number(n.into()),
1022+
Destructured::String(s) => serde_json::Value::String(s.as_str().to_owned()),
1023+
Destructured::Array(a) => {
1024+
serde_json::Value::Array(a.into_iter().map(Into::into).collect())
1025+
}
1026+
Destructured::Object(o) => serde_json::Value::Object(
1027+
o.into_iter()
1028+
.map(|(k, v)| (k.as_str().to_owned(), v.into()))
1029+
.collect(),
1030+
),
1031+
}
1032+
}
1033+
}
1034+
9761035
impl Default for IValue {
9771036
fn default() -> Self {
9781037
Self::NULL
@@ -1115,4 +1174,47 @@ mod tests {
11151174

11161175
assert_eq!(x.into_object(), Ok(o));
11171176
}
1177+
1178+
#[mockalloc::test]
1179+
fn test_from_iter_array() {
1180+
let x: IValue = (0..5).collect();
1181+
let y: IValue = ijson!([0, 1, 2, 3, 4]);
1182+
assert_eq!(x, y);
1183+
1184+
let empty: IValue = std::iter::empty::<i32>().collect();
1185+
assert_eq!(empty, ijson!([]));
1186+
}
1187+
1188+
#[mockalloc::test]
1189+
fn test_from_iter_object() {
1190+
let x: IValue = (0..3).map(|i| (i.to_string(), i)).collect();
1191+
let y: IValue = ijson!({"0": 0, "1": 1, "2": 2});
1192+
assert_eq!(x, y);
1193+
1194+
let empty: IValue = std::iter::empty::<(String, i32)>().collect();
1195+
assert_eq!(empty, ijson!({}));
1196+
}
1197+
1198+
#[mockalloc::test]
1199+
fn test_serde_json_roundtrip() {
1200+
let json = serde_json::json!({
1201+
"null": null,
1202+
"bool": true,
1203+
"int": 42,
1204+
"neg": -17,
1205+
"big": 18446744073709551615u64,
1206+
"float": 63.5,
1207+
"string": "hello",
1208+
"array": [1, 2, 3, "four", false, null],
1209+
"object": {"nested": [1.5, {"deep": true}]}
1210+
});
1211+
1212+
let ivalue: IValue = json.clone().into();
1213+
let back: serde_json::Value = ivalue.clone().into();
1214+
assert_eq!(json, back);
1215+
1216+
// Also check consistency with the serde-based conversion.
1217+
let via_serde: IValue = crate::to_value(&json).unwrap();
1218+
assert_eq!(ivalue, via_serde);
1219+
}
11181220
}

0 commit comments

Comments
 (0)