使用pyo3将内层为serde_json::Value的嵌套HashMap导出到Python
问题描述
我用Rust编写了一个文本解析器,希望通过pyo3为其提供Python调用接口。该解析器返回嵌套HashMap结构,内层HashMap的值类型为serde_json::Value,我尝试将该结构作为PyObject返回时遇到了无法解决的错误。
最小复现代码
use std::collections::HashMap; use pyo3::prelude::*; use serde_json::Value; #[pyfunction] pub fn parse() -> PyResult<PyObject> { let mapping: HashMap<i64, HashMap<String, Value>> = HashMap::from( [ ( 1, HashMap::from( [ ( "test11".to_string(), "Foo".into() ), ( "test12".to_string(), 123.into() ), ] ) ), ( 2, HashMap::from( [ ( "test21".to_string(), "Bar".into() ), ( "test22".to_string(), 123.45.into() ), ] ) ), ] ); return pyo3::Python::with_gil( |py| { Ok( mapping.to_object( py ) ) } ); } #[pymodule] fn parser( _py: Python, m: &PyModule ) -> PyResult<()> { m.add_function( wrap_pyfunction!( parse, m )? )?; return Ok( () ); }
报错信息
error[E0599]: the method `to_object` exists for struct `HashMap<i64, HashMap<std::string::String, Value>>`, but its trait bounds were not satisfied --> src/lib.rs:22:15 | 22 | Ok( mapping.to_object( py ) ) | ^^^^^^^^^ method cannot be called on `HashMap<i64, HashMap<std::string::String, Value>>` due to unsatisfied trait bounds | ::: /home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/collections/hash/map.rs:209:1 | 209 | pub struct HashMap<K, V, S = RandomState> { | ----------------------------------------- doesn't satisfy `_: pyo3::ToPyObject` | = note: the following trait bounds were not satisfied: `HashMap<std::string::String, Value>: pyo3::ToPyObject` which is required by `HashMap<i64, HashMap<std::string::String, Value>>: pyo3::ToPyObject` error[E0277]: the trait bound `Result<PyDict, PyErr>: IntoPyCallbackOutput<_>` is not satisfied --> src/lib.rs:8:1 | 8 | #[pyfunction] | ^^^^^^^^^^^^^ the trait `IntoPyCallbackOutput<_>` is not implemented for `Result<PyDict, PyErr>` | = help: the following implementations were found: <Result<T, E> as IntoPyCallbackOutput<U>> note: required by a bound in `pyo3::callback::convert` --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/pyo3-0.14.5/src/callback.rs:182:8 | 182 | T: IntoPyCallbackOutput<U>, | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `pyo3::callback::convert` = note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)
预期返回效果
在Python中调用该函数时,返回如下结构的字典:
{ 1: { "test11": "Foo", "test12": 123, }, 2: { "test21": "Bar", "test22": 123.45, }, }
解决方案
(基于@orlp的回答实现)
use std::collections::HashMap; use pyo3::prelude::*; use serde_json::Value; fn value_to_object( val: &Value, py: Python<'_> ) -> PyObject { match val { Value::Null => py.None(), Value::Bool( x ) => x.to_object( py ), Value::Number( x ) => { let oi64 = x.as_i64().map( |i| i.to_object( py ) ); let ou64 = x.as_u64().map( |i| i.to_object( py ) ); let of64 = x.as_f64().map( |i| i.to_object( py ) ); oi64.or( ou64 ).or( of64 ).expect( "number too large" ) }, Value::String( x ) => x.to_object( py ), Value::Array( x ) => { let inner: Vec<_> = x.iter().map(|x| value_to_object(x, py)).collect(); inner.to_object( py ) }, Value::Object( x ) => { let inner: HashMap<_, _> = x.iter() .map( |( k, v )| ( k, value_to_object( v, py ) ) ).collect(); inner.to_object( py ) }, } } #[repr(transparent)] #[derive( Clone, Debug )] struct ParsedValue( Value ); impl ToPyObject for ParsedValue { fn to_object( &self, py: Python<'_> ) -> PyObject { value_to_object( &self.0, py ) } } #[pyfunction] pub fn parse() -> PyResult<PyObject> { let mapping: HashMap<i64, HashMap<String, ParsedValue>> = HashMap::from( [ ( 1, HashMap::from( [ ( "test11".to_string(), ParsedValue( "Foo".into() ) ), ( "test12".to_string(), ParsedValue( 123.into() ) ), ] ) ), ( 2, HashMap::from( [ ( "test21".to_string(), ParsedValue( "Bar".into() ) ), ( "test22".to_string(), ParsedValue( 123.45.into() ) ), ] ) ), ] ); Ok( pyo3::Python::with_gil( |py| { mapping.to_object( py ) } ) ) } #[pymodule] fn parser( _py: Python, m: &PyModule ) -> PyResult<()> { m.add_function( wrap_pyfunction!( parse, m )? )?; return Ok( () ); }
实现说明
错误的核心原因是serde_json::Value没有实现pyo3的ToPyObject trait,导致嵌套HashMap无法直接转换为Python对象。解决方案通过自定义ParsedValue包装serde_json::Value,手动实现ToPyObject trait,递归把serde_json的所有类型对应转换为Python原生对象,满足转换要求。
内容的提问来源于stack exchange,提问作者leviathan
相关产品推荐
相关产品推荐

