Skip to content

Commit ed18c84

Browse files
committed
Deserialization avoids pyo3 borrows, uses FFI for list and dict
1 parent 4eed3fd commit ed18c84

3 files changed

Lines changed: 47 additions & 24 deletions

File tree

src/decode.rs

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use pyo3::prelude::*;
44
use pyo3::types::*;
5+
use pyo3::IntoPyPointer;
56
use serde::de::{self, DeserializeSeed, Deserializer, MapAccess, SeqAccess, Visitor};
67
use smallvec::SmallVec;
78
use std::borrow::Cow;
@@ -14,11 +15,11 @@ pub fn deserialize(py: Python, data: &str) -> PyResult<PyObject> {
1415
let seed = JsonValue::new(py);
1516
let mut deserializer = serde_json::Deserializer::from_str(data);
1617
match seed.deserialize(&mut deserializer) {
17-
Ok(py_object) => {
18+
Ok(py_ptr) => {
1819
deserializer
1920
.end()
2021
.map_err(|e| JSONDecodeError::py_err((e.to_string(), "", 0)))?;
21-
Ok(py_object)
22+
Ok(unsafe { PyObject::from_owned_ptr(py, py_ptr) })
2223
}
2324
Err(e) => {
2425
return Err(JSONDecodeError::py_err((e.to_string(), "", 0)));
@@ -38,7 +39,7 @@ impl<'a> JsonValue<'a> {
3839
}
3940

4041
impl<'de, 'a> DeserializeSeed<'de> for JsonValue<'a> {
41-
type Value = PyObject;
42+
type Value = *mut pyo3::ffi::PyObject;
4243

4344
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
4445
where
@@ -49,81 +50,85 @@ impl<'de, 'a> DeserializeSeed<'de> for JsonValue<'a> {
4950
}
5051

5152
impl<'de, 'a> Visitor<'de> for JsonValue<'a> {
52-
type Value = PyObject;
53+
type Value = *mut pyo3::ffi::PyObject;
5354

5455
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
5556
formatter.write_str("JSON")
5657
}
5758

5859
fn visit_unit<E>(self) -> Result<Self::Value, E> {
59-
Ok(self.py.None())
60+
Ok(unsafe { pyo3::ffi::Py_None() })
6061
}
6162

6263
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
6364
where
6465
E: de::Error,
6566
{
66-
Ok(value.to_object(self.py))
67+
Ok(value.into_object(self.py).into_ptr())
6768
}
6869

6970
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
7071
where
7172
E: de::Error,
7273
{
73-
Ok(value.to_object(self.py))
74+
Ok(value.into_object(self.py).into_ptr())
7475
}
7576

7677
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
7778
where
7879
E: de::Error,
7980
{
80-
Ok(value.to_object(self.py))
81+
Ok(value.into_object(self.py).into_ptr())
8182
}
8283

8384
fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
8485
where
8586
E: de::Error,
8687
{
87-
Ok(value.to_object(self.py))
88+
Ok(PyFloat::new(self.py, value).into_ptr())
8889
}
8990

9091
fn visit_borrowed_str<E>(self, value: &str) -> Result<Self::Value, E>
9192
where
9293
E: de::Error,
9394
{
94-
Ok(value.to_object(self.py))
95+
Ok(PyString::new(self.py, value).into_ptr())
9596
}
9697

9798
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
9899
where
99100
E: de::Error,
100101
{
101-
Ok(value.to_object(self.py))
102+
Ok(PyString::new(self.py, value).into_ptr())
102103
}
103104

104105
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
105106
where
106107
A: SeqAccess<'de>,
107108
{
108-
let mut elements: SmallVec<[PyObject; 8]> = SmallVec::new();
109+
let mut elements: SmallVec<[*mut pyo3::ffi::PyObject; 8]> = SmallVec::new();
109110
while let Some(elem) = seq.next_element_seed(self.clone())? {
110111
elements.push(elem);
111112
}
112-
Ok(elements.as_slice().to_object(self.py))
113+
let ptr = unsafe { pyo3::ffi::PyList_New(elements.len() as pyo3::ffi::Py_ssize_t) };
114+
for (i, obj) in elements.iter().enumerate() {
115+
unsafe { pyo3::ffi::PyList_SetItem(ptr, i as pyo3::ffi::Py_ssize_t, *obj) };
116+
}
117+
Ok(ptr)
113118
}
114119

115120
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
116121
where
117122
A: MapAccess<'de>,
118123
{
119-
let mut elements: SmallVec<[(PyObject, PyObject); 8]> = SmallVec::new();
124+
let dict_ptr = PyDict::new(self.py).into_ptr();
120125
while let Some((key, value)) = map.next_entry_seed(PhantomData::<Cow<str>>, self.clone())? {
121-
elements.push((key.to_object(self.py), value));
122-
}
123-
let dict = PyDict::new(self.py);
124-
for (key, value) in elements.iter() {
125-
dict.set_item(key, value).unwrap()
126+
let _ = unsafe { pyo3::ffi::PyDict_SetItem(
127+
dict_ptr,
128+
PyString::new(self.py, &key).into_ptr(),
129+
value,
130+
) };
126131
}
127-
Ok(dict.into())
132+
Ok(dict_ptr)
128133
}
129134
}

src/typeref.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ pub fn init_typerefs(py: Python) {
2424
LIST_PTR = PyList::empty(py).as_ref().get_type_ptr();
2525
TUPLE_PTR = PyTuple::empty(py).as_ref(py).get_type_ptr();
2626
NONE_PTR = py.None().as_ref(py).get_type_ptr();
27-
BOOL_PTR = true.to_object(py).as_ref(py).get_type_ptr();
28-
INT_PTR = 1.to_object(py).as_ref(py).get_type_ptr();
29-
FLOAT_PTR = 1.0.to_object(py).as_ref(py).get_type_ptr();
27+
BOOL_PTR = true.into_object(py).as_ref(py).get_type_ptr();
28+
INT_PTR = 1.into_object(py).as_ref(py).get_type_ptr();
29+
FLOAT_PTR = 1.0.into_object(py).as_ref(py).get_type_ptr();
3030
});
3131
}

test/test_type.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,33 @@ def test_bool(self):
3131
self.assertEqual(orjson.dumps(obj), ref.encode('utf-8'))
3232
self.assertEqual(orjson.loads(ref), obj)
3333

34+
def test_bool_array(self):
35+
"""
36+
bool array
37+
"""
38+
obj = [True] * 256
39+
ref = ('[' + ('true,' * 255) + 'true]').encode('utf-8')
40+
self.assertEqual(orjson.dumps(obj), ref)
41+
self.assertEqual(orjson.loads(ref), obj)
42+
3443
def test_none(self):
3544
"""
36-
NoneType
45+
null
3746
"""
3847
obj = None
3948
ref = u'null'
4049
self.assertEqual(orjson.dumps(obj), ref.encode('utf-8'))
4150
self.assertEqual(orjson.loads(ref), obj)
4251

52+
def test_null_array(self):
53+
"""
54+
null array
55+
"""
56+
obj = [None] * 256
57+
ref = ('[' + ('null,' * 255) + 'null]').encode('utf-8')
58+
self.assertEqual(orjson.dumps(obj), ref)
59+
self.assertEqual(orjson.loads(ref), obj)
60+
4361
def test_int_64(self):
4462
"""
4563
int 64-bit

0 commit comments

Comments
 (0)