lindera/
util.rs

1//! Utility functions for Python-Rust data conversion.
2//!
3//! This module provides helper functions for converting between Python objects
4//! and Rust data structures, particularly for working with JSON-like data.
5
6use std::collections::HashMap;
7
8use pyo3::exceptions::PyTypeError;
9use pyo3::prelude::*;
10use pyo3::types::{PyBool, PyDict, PyFloat, PyInt, PyList, PyNone, PyString};
11use serde_json::{Value, json};
12
13/// Converts a Python object to a serde_json::Value.
14///
15/// # Arguments
16///
17/// * `value` - Python object to convert.
18///
19/// # Returns
20///
21/// A `serde_json::Value` representing the Python object.
22///
23/// # Errors
24///
25/// Returns an error if the Python object type is not supported.
26pub fn pyany_to_value(value: &Bound<'_, PyAny>) -> PyResult<Value> {
27    if value.is_instance_of::<PyString>() {
28        Ok(Value::from(value.extract::<String>()?))
29    } else if value.is_instance_of::<PyBool>() {
30        Ok(Value::from(value.extract::<bool>()?))
31    } else if value.is_instance_of::<PyFloat>() {
32        Ok(Value::from(value.extract::<f64>()?))
33    } else if value.is_instance_of::<PyInt>() {
34        Ok(Value::from(value.extract::<i64>()?))
35    } else if value.is_instance_of::<PyList>() {
36        pylist_to_value(&value.extract::<Bound<'_, PyList>>()?)
37    } else if value.is_instance_of::<PyDict>() {
38        pydict_to_value(&value.extract::<Bound<'_, PyDict>>()?)
39    } else if value.is_instance_of::<PyNone>() {
40        Ok(Value::Null)
41    } else {
42        Err(PyErr::new::<PyTypeError, _>(format!(
43            "Unsupported Python object: {value}"
44        )))
45    }
46}
47
48fn pylist_to_value(pylist: &Bound<'_, PyList>) -> PyResult<Value> {
49    let mut vec: Vec<Value> = Vec::new();
50    for value in pylist.into_iter() {
51        vec.push(pyany_to_value(&value)?);
52    }
53    Ok(vec.into())
54}
55
56/// Converts a Python dictionary to a serde_json::Value.
57///
58/// # Arguments
59///
60/// * `pydict` - Python dictionary to convert.
61///
62/// # Returns
63///
64/// A `serde_json::Value` representing the dictionary.
65pub fn pydict_to_value(pydict: &Bound<'_, PyDict>) -> PyResult<Value> {
66    let mut map: HashMap<String, Value> = HashMap::new();
67    for (key, value) in pydict.into_iter() {
68        map.insert(key.extract::<String>()?, pyany_to_value(&value)?);
69    }
70    Ok(json!(map))
71}
72
73/// Converts a serde_json::Value to a Python object.
74///
75/// # Arguments
76///
77/// * `py` - Python GIL token.
78/// * `value` - JSON value to convert.
79///
80/// # Returns
81///
82/// A Python object representing the JSON value.
83pub fn value_to_pydict(py: Python, value: &Value) -> PyResult<Py<PyAny>> {
84    match value {
85        Value::Null => Ok(py.None()),
86        Value::Bool(b) => Ok(PyBool::new(py, *b).into_pyobject(py)?.to_owned().into()),
87        Value::Number(num) => {
88            if let Some(i) = num.as_i64() {
89                Ok(i.into_pyobject(py)?.into())
90            } else if let Some(f) = num.as_f64() {
91                Ok(f.into_pyobject(py)?.into())
92            } else {
93                Err(PyTypeError::new_err("Unsupported number type"))
94            }
95        }
96        Value::String(s) => Ok(PyString::new(py, s).into_pyobject(py)?.into()),
97        Value::Array(arr) => {
98            let py_list = PyList::empty(py);
99            for item in arr {
100                py_list.append(value_to_pydict(py, item)?)?;
101            }
102            Ok(py_list.into())
103        }
104        Value::Object(obj) => {
105            let py_dict = PyDict::new(py);
106            for (key, val) in obj {
107                py_dict.set_item(key, value_to_pydict(py, val)?)?;
108            }
109            Ok(py_dict.into())
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {}