google-apis-rs / google-cloud-rs

Asynchronous Rust bindings for Google Cloud Platform APIs.
176 stars 48 forks source link

Use of serde for Datastore encoding/decoding #55

Closed plippe closed 3 years ago

plippe commented 3 years ago

Hi,

Serde is a create library to encode and decode values. The list of supported data formats is quite large and contains json.

Are there any reasons Serde wasn't used for Google's Datastore?

plippe commented 3 years ago

I have written some helpers for my own application. Beware, my solution doesn't handle all cases. I have mapped some types to Null instead of properly handling the issue.

fn recursive_into_value_with_serialize(value: serde_json::value::Value) -> Value {
    match value {
        serde_json::value::Value::Null => Value::EntityValue(HashMap::new()),
        serde_json::value::Value::Bool(b) => Value::BooleanValue(b),
        serde_json::value::Value::String(s) => Value::StringValue(s),
        serde_json::value::Value::Number(n) => n
            .as_i64()
            .map(Value::IntegerValue)
            .or_else(|| n.as_f64().map(Value::DoubleValue))
            .unwrap(),
        serde_json::value::Value::Array(vec) => {
            let vec = vec
                .into_iter()
                .map(recursive_into_value_with_serialize)
                .collect::<Vec<Value>>();
            Value::ArrayValue(vec)
        }
        serde_json::value::Value::Object(map) => {
            let map = map
                .into_iter()
                .map(|(k, v)| (k, recursive_into_value_with_serialize(v)))
                .collect::<HashMap<String, Value>>();
            Value::EntityValue(map)
        }
    }
}

pub fn into_value_with_serialize<A>(value: A) -> Value
where
    A: serde::Serialize,
{
    let value = serde_json::to_value(value).unwrap();
    recursive_into_value_with_serialize(value)
}

fn recursive_from_value_with_deserialize(value: Value) -> serde_json::value::Value {
    match value {
        Value::BooleanValue(b) => serde_json::value::Value::Bool(b),
        Value::IntegerValue(i) => serde_json::value::Value::Number(i.into()),
        Value::DoubleValue(f) => {
            serde_json::value::Value::Number(serde_json::Number::from_f64(f).unwrap())
        }
        Value::StringValue(s) => serde_json::value::Value::String(s),
        Value::EntityValue(map) if map.is_empty() => serde_json::value::Value::Null,
        Value::EntityValue(map) => {
            let map = map
                .into_iter()
                .map(|(k, v)| (k, recursive_from_value_with_deserialize(v)))
                .collect::<serde_json::map::Map<String, serde_json::value::Value>>();
            serde_json::value::Value::Object(map)
        }
        Value::ArrayValue(vec) => {
            let vec = vec
                .into_iter()
                .map(recursive_from_value_with_deserialize)
                .collect::<Vec<serde_json::value::Value>>();
            serde_json::value::Value::Array(vec)
        }
        Value::KeyValue(_) => serde_json::value::Value::Null,
        Value::TimestampValue(_) => serde_json::value::Value::Null,
        Value::BlobValue(_) => serde_json::value::Value::Null,
        Value::GeoPointValue(_, _) => serde_json::value::Value::Null,
    }
}

pub fn from_value_with_deserialize<A>(
    value: google_cloud::datastore::Value,
) -> std::result::Result<A, ConvertError>
where
    A: for<'de> serde::Deserialize<'de>,
{
    let value = recursive_from_value_with_deserialize(value);
    serde_json::from_value::<A>(value).map_err(|_| ConvertError::MissingProperty("".to_owned()))
}

Lastly, those helpers are used in the IntoValue and FromValue implementations.

impl FromValue for MyType {
    fn from_value(value: Value) -> std::result::Result<Self, ConvertError> {
        datastore::from_value_with_deserialize::<Self>(value)
    }
}

impl IntoValue for MyType {
    fn into_value(self) -> Value {
        datastore::into_value_with_serialize::<Self>(self)
    }
}

Hope this helps others and possibly makes it into the next version of the crate.