google-apis-rs/google-cloud-rs

Use of serde for Datastore encoding/decoding

plippe opened this issue · 1 comments

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?

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.