Skip to content
Merged
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
21 changes: 21 additions & 0 deletions tests/snippets/json_snippet.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,19 @@ def round_trip_test(obj):
assert "1.0" == json.dumps(1.0)
assert "true" == json.dumps(True)
assert "false" == json.dumps(False)
assert 'null' == json.dumps(None)

assert '[]' == json.dumps([])
assert '[1]' == json.dumps([1])
assert '[[1]]' == json.dumps([[1]])
round_trip_test([1, "string", 1.0, True])

assert '[]' == json.dumps(())
assert '[1]' == json.dumps((1,))
assert '[[1]]' == json.dumps(((1,),))
# tuples don't round-trip through json
assert [1, "string", 1.0, True] == json.loads(json.dumps((1, "string", 1.0, True)))

assert '{}' == json.dumps({})
# TODO: uncomment once dict comparison is implemented
# round_trip_test({'a': 'b'})
Expand All @@ -35,3 +42,17 @@ def round_trip_test(obj):
assert [] == json.loads('[]')
assert ['a'] == json.loads('["a"]')
assert [['a'], 'b'] == json.loads('[["a"], "b"]')

class String(str): pass

assert '"string"' == json.dumps(String("string"))

# TODO: Uncomment and test once int/float construction is supported
# class Int(int): pass
# class Float(float): pass

# TODO: Uncomment and test once sequence/dict subclasses are supported by
# json.dumps
# class List(list): pass
# class Tuple(tuple): pass
# class Dict(dict): pass
2 changes: 1 addition & 1 deletion vm/src/objdict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn new(dict_type: PyObjectRef) -> PyObjectRef {
)
}

fn get_elements(obj: &PyObjectRef) -> HashMap<String, PyObjectRef> {
pub fn get_elements(obj: &PyObjectRef) -> HashMap<String, PyObjectRef> {
if let PyObjectKind::Dict { elements } = &obj.borrow().kind {
elements.clone()
} else {
Expand Down
2 changes: 1 addition & 1 deletion vm/src/objlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn set_item(
}
}

fn get_elements(obj: PyObjectRef) -> Vec<PyObjectRef> {
pub fn get_elements(obj: PyObjectRef) -> Vec<PyObjectRef> {
if let PyObjectKind::List { elements } = &obj.borrow().kind {
elements.to_vec()
} else {
Expand Down
8 changes: 8 additions & 0 deletions vm/src/objsequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,11 @@ pub fn get_item(
))),
}
}

pub fn get_elements(obj: PyObjectRef) -> Vec<PyObjectRef> {
if let PyObjectKind::Tuple { elements } = &obj.borrow().kind {
elements.to_vec()
} else {
panic!("Cannot extract list elements from non-list");
}
}
72 changes: 51 additions & 21 deletions vm/src/stdlib/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,70 @@ use serde::de::Visitor;
use serde::ser::{SerializeMap, SerializeSeq};
use serde_json;

use super::super::objtype;
use super::super::pyobject::{
DictProtocol, PyContext, PyFuncArgs, PyObject, PyObjectKind, PyObjectRef, PyResult,
TypeProtocol,
};
use super::super::VirtualMachine;
use super::super::{objbool, objdict, objfloat, objint, objlist, objsequence, objstr, objtype};

impl serde::Serialize for PyObjectKind {
// We need to have a VM available to serialise a PyObject based on its subclass, so we implement
// PyObject serialisation via a proxy object which holds a reference to a VM
struct PyObjectSerializer<'s> {
pyobject: &'s PyObjectRef,
vm: &'s VirtualMachine,
}

impl<'s> PyObjectSerializer<'s> {
fn clone_with_object(&self, pyobject: &'s PyObjectRef) -> PyObjectSerializer {
PyObjectSerializer {
pyobject,
vm: self.vm,
}
}
}

impl<'s> serde::Serialize for PyObjectSerializer<'s> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
PyObjectKind::String { value } => serializer.serialize_str(value),
PyObjectKind::Integer { value } => serializer.serialize_i32(*value),
PyObjectKind::Float { value } => serializer.serialize_f64(*value),
PyObjectKind::Boolean { value } => serializer.serialize_bool(*value),
PyObjectKind::List { elements } | PyObjectKind::Tuple { elements } => {
let serialize_seq_elements =
|serializer: S, elements: Vec<PyObjectRef>| -> Result<S::Ok, S::Error> {
let mut seq = serializer.serialize_seq(Some(elements.len()))?;
for e in elements {
match e.borrow().kind {
ref kind => seq.serialize_element(kind)?,
}
seq.serialize_element(&self.clone_with_object(&e))?;
}
seq.end()
};
if objtype::isinstance(self.pyobject.clone(), self.vm.ctx.str_type()) {
serializer.serialize_str(&objstr::get_value(&self.pyobject))
} else if objtype::isinstance(self.pyobject.clone(), self.vm.ctx.int_type()) {
serializer.serialize_i32(objint::get_value(self.pyobject.clone()))
} else if objtype::isinstance(self.pyobject.clone(), self.vm.ctx.float_type()) {
serializer.serialize_f64(objfloat::get_value(self.pyobject.clone()))
} else if objtype::isinstance(self.pyobject.clone(), self.vm.ctx.bool_type()) {
serializer.serialize_bool(objbool::get_value(self.pyobject))
} else if objtype::isinstance(self.pyobject.clone(), self.vm.ctx.list_type()) {
let elements = objlist::get_elements(self.pyobject.clone());
serialize_seq_elements(serializer, elements)
} else if objtype::isinstance(self.pyobject.clone(), self.vm.ctx.tuple_type()) {
let elements = objsequence::get_elements(self.pyobject.clone());
serialize_seq_elements(serializer, elements)
} else if objtype::isinstance(self.pyobject.clone(), self.vm.ctx.dict_type()) {
let elements = objdict::get_elements(self.pyobject);
let mut map = serializer.serialize_map(Some(elements.len()))?;
for (key, e) in elements {
map.serialize_entry(&key, &self.clone_with_object(&e))?;
}
PyObjectKind::Dict { elements } => {
let mut map = serializer.serialize_map(Some(elements.len()))?;
for (key, e) in elements {
map.serialize_entry(key, &e.borrow().kind)?;
}
map.end()
}
PyObjectKind::None => serializer.serialize_none(),
kind => unimplemented!("Object of type '{:?}' is not serializable", kind),
map.end()
} else if let PyObjectKind::None = self.pyobject.borrow().kind {
serializer.serialize_none()
} else {
unimplemented!(
"Object of type '{:?}' is not serializable",
self.pyobject.typ()
);
}
}
}
Expand Down Expand Up @@ -172,7 +201,8 @@ fn dumps(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
// TODO: Implement non-trivial serialisation case
arg_check!(vm, args, required = [(obj, None)]);
// TODO: Raise an exception for serialisation errors
let string = serde_json::to_string(&obj.borrow().kind).unwrap();
let serializer = PyObjectSerializer { pyobject: obj, vm };
let string = serde_json::to_string(&serializer).unwrap();
Ok(vm.context().new_str(string))
}

Expand Down