Skip to content

Commit 6c29f3f

Browse files
authored
Convert your custom struct to/from a single Feature (#253)
analogous to serde_json::{to_value,from_value} Your custom struct must have a `geometry` field, all other fields will be mapped to feature.properties.
1 parent 341f937 commit 6c29f3f

File tree

3 files changed

+188
-1
lines changed

3 files changed

+188
-1
lines changed

CHANGES.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
* Add `to_feature` to convert a single S: Serialize to a Feature
6+
* Add `from_feature` to convert a single Feature to a D: Deserialize
57
* Upgrade from thiserror v1 to v2
68
* Add support of serializing optional `geo-types` with `serialize_optional_geometry`.
79
* Add support of deserializing optional `geo-types` with `deserialize_optional_geometry`.

src/de.rs

+86
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,57 @@ where
348348
Ok(deserializer.deserialize_map(visitor)?)
349349
}
350350

351+
/// Interpret a [`Feature`] as an instance of type `T`.
352+
///
353+
/// This is analogous to [`serde_json::from_value`](https://docs.rs/serde_json/latest/serde_json/fn.from_value.html)
354+
///
355+
/// `T`'s `geometry` field will be deserialized from `feature.geometry`.
356+
/// All other fields will be deserialized from `feature.properties`.
357+
///
358+
/// # Examples
359+
#[cfg_attr(feature = "geo-types", doc = "```")]
360+
#[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
361+
/// use serde::Deserialize;
362+
/// use geojson::Feature;
363+
/// use geojson::de::{from_feature, deserialize_geometry, deserialize_single_feature};
364+
/// use std::str::FromStr;
365+
///
366+
/// #[derive(Deserialize)]
367+
/// struct MyStruct {
368+
/// // Deserialize `geometry` as GeoJSON, rather than using the type's default deserialization
369+
/// #[serde(deserialize_with = "deserialize_geometry")]
370+
/// geometry: geo_types::Point,
371+
/// name: String,
372+
/// }
373+
///
374+
/// let geojson_str = r#"{
375+
/// "type": "Feature",
376+
/// "geometry": { "type": "Point", "coordinates": [1.0, 2.0] },
377+
/// "properties": {
378+
/// "name": "My Name"
379+
/// }
380+
/// }"#;
381+
/// let feature = Feature::from_str(geojson_str).unwrap();
382+
///
383+
/// let my_struct: MyStruct = from_feature(feature).unwrap();
384+
///
385+
/// assert_eq!("My Name", my_struct.name);
386+
/// assert_eq!(geo_types::Point::new(1.0, 2.0), my_struct.geometry);
387+
/// ```
388+
///
389+
/// # Errors
390+
///
391+
/// Deserialization can fail if `T`'s implementation of `Deserialize` decides to fail.
392+
pub fn from_feature<'de, T>(feature: Feature) -> Result<T>
393+
where
394+
T: Deserialize<'de>,
395+
{
396+
let feature_value: JsonValue = serde_json::to_value(feature)?;
397+
let deserializer = feature_value.into_deserializer();
398+
let visitor = FeatureVisitor::new();
399+
Ok(deserializer.deserialize_map(visitor)?)
400+
}
401+
351402
struct FeatureVisitor<D> {
352403
_marker: PhantomData<D>,
353404
}
@@ -611,6 +662,41 @@ pub(crate) mod tests {
611662
let feature: MyStruct = serde_json::from_value(json).unwrap();
612663
assert!(feature.geometry.is_none())
613664
}
665+
666+
#[test]
667+
fn test_from_feature() {
668+
#[derive(Debug, PartialEq, Deserialize)]
669+
struct MyStruct {
670+
#[serde(deserialize_with = "deserialize_geometry")]
671+
geometry: geo_types::Point<f64>,
672+
name: String,
673+
age: u64,
674+
}
675+
676+
let feature = Feature {
677+
bbox: None,
678+
geometry: Some(crate::Geometry::new(crate::Value::Point(vec![125.6, 10.1]))),
679+
id: None,
680+
properties: Some(
681+
json!({
682+
"name": "Dinagat Islands",
683+
"age": 123,
684+
})
685+
.as_object()
686+
.unwrap()
687+
.clone(),
688+
),
689+
foreign_members: None,
690+
};
691+
692+
let actual: MyStruct = from_feature(feature).unwrap();
693+
let expected = MyStruct {
694+
geometry: geo_types::Point::new(125.6, 10.1),
695+
name: "Dinagat Islands".to_string(),
696+
age: 123,
697+
};
698+
assert_eq!(actual, expected);
699+
}
614700
}
615701

616702
#[cfg(feature = "geo-types")]

src/ser.rs

+100-1
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,12 @@
9696
//! ...
9797
//! }
9898
//! ```
99-
use crate::{JsonObject, JsonValue, Result};
99+
use crate::{Feature, JsonObject, JsonValue, Result};
100100

101101
use serde::{ser::Error, Serialize, Serializer};
102102

103+
use crate::util::expect_owned_object;
104+
use std::convert::TryFrom;
103105
use std::{convert::TryInto, io};
104106

105107
/// Serialize a single data structure to a GeoJSON Feature string.
@@ -198,6 +200,66 @@ where
198200
Ok(())
199201
}
200202

203+
/// Convert a `T` into a [`Feature`].
204+
///
205+
/// This is analogous to [`serde_json::to_value`](https://docs.rs/serde_json/latest/serde_json/fn.to_value.html)
206+
///
207+
/// Note that if (and only if) `T` has a field named `geometry`, it will be serialized to
208+
/// `feature.geometry`.
209+
///
210+
/// All other fields will be serialized to `feature.properties`.
211+
///
212+
/// # Examples
213+
#[cfg_attr(feature = "geo-types", doc = "```")]
214+
#[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
215+
/// use serde::Serialize;
216+
/// use geojson::{Feature, Value, Geometry};
217+
/// use geojson::ser::{to_feature, serialize_geometry};
218+
///
219+
/// #[derive(Serialize)]
220+
/// struct MyStruct {
221+
/// // Serialize `geometry` as geojson, rather than using the type's default serialization
222+
/// #[serde(serialize_with = "serialize_geometry")]
223+
/// geometry: geo_types::Point,
224+
/// name: String,
225+
/// }
226+
///
227+
/// let my_struct = MyStruct {
228+
/// geometry: geo_types::Point::new(1.0, 2.0),
229+
/// name: "My Name".to_string()
230+
/// };
231+
///
232+
/// let feature: Feature = to_feature(my_struct).unwrap();
233+
/// assert_eq!("My Name", feature.properties.unwrap()["name"]);
234+
/// assert_eq!(feature.geometry.unwrap(), Geometry::new(Value::Point(vec![1.0, 2.0])));
235+
/// ```
236+
///
237+
/// # Errors
238+
///
239+
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
240+
/// fail, or if `T` contains a map with non-string keys.
241+
pub fn to_feature<T>(value: T) -> Result<Feature>
242+
where
243+
T: Serialize,
244+
{
245+
let js_value = serde_json::to_value(value)?;
246+
let mut js_object = expect_owned_object(js_value)?;
247+
248+
let geometry = if let Some(geometry_value) = js_object.remove("geometry") {
249+
Some(crate::Geometry::try_from(geometry_value)?)
250+
} else {
251+
None
252+
};
253+
254+
Ok(Feature {
255+
bbox: None,
256+
geometry,
257+
id: None,
258+
properties: Some(js_object),
259+
foreign_members: None,
260+
})
261+
}
262+
201263
/// Serialize elements as a GeoJSON FeatureCollection into the IO stream.
202264
///
203265
/// Note that `T` must have a column called `geometry`.
@@ -569,6 +631,7 @@ mod tests {
569631
mod geo_types_tests {
570632
use super::*;
571633
use crate::de::tests::feature_collection;
634+
use crate::Geometry;
572635

573636
#[test]
574637
fn serializes_optional_point() {
@@ -677,6 +740,42 @@ mod tests {
677740
assert_eq!(actual_output, expected_output);
678741
}
679742

743+
#[test]
744+
fn test_to_feature() {
745+
#[derive(Serialize)]
746+
struct MyStruct {
747+
#[serde(serialize_with = "serialize_geometry")]
748+
geometry: geo_types::Point<f64>,
749+
name: String,
750+
age: u64,
751+
}
752+
753+
let my_struct = MyStruct {
754+
geometry: geo_types::point!(x: 125.6, y: 10.1),
755+
name: "Dinagat Islands".to_string(),
756+
age: 123,
757+
};
758+
759+
let actual = to_feature(&my_struct).unwrap();
760+
let expected = Feature {
761+
bbox: None,
762+
geometry: Some(Geometry::new(crate::Value::Point(vec![125.6, 10.1]))),
763+
id: None,
764+
properties: Some(
765+
json!({
766+
"name": "Dinagat Islands",
767+
"age": 123
768+
})
769+
.as_object()
770+
.unwrap()
771+
.clone(),
772+
),
773+
foreign_members: None,
774+
};
775+
776+
assert_eq!(actual, expected)
777+
}
778+
680779
#[test]
681780
fn serialize_feature_collection() {
682781
#[derive(Serialize)]

0 commit comments

Comments
 (0)