#![cfg_attr(feature = "geo-types", doc = "```")]
#![cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
use crate::{Feature, FeatureReader, JsonValue, Result};
use std::convert::{TryFrom, TryInto};
use std::fmt::Formatter;
use std::io::Read;
use std::marker::PhantomData;
use serde::de::{Deserialize, Deserializer, Error, IntoDeserializer};
#[cfg_attr(feature = "geo-types", doc = "```")]
#[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
pub fn deserialize_feature_collection<'de, T>(
feature_collection_reader: impl Read,
) -> Result<impl Iterator<Item = Result<T>>>
where
T: Deserialize<'de>,
{
#[allow(deprecated)]
let iter = crate::FeatureIterator::new(feature_collection_reader).map(
|feature_value: Result<JsonValue>| {
let deserializer = feature_value?.into_deserializer();
let visitor = FeatureVisitor::new();
let record: T = deserializer.deserialize_map(visitor)?;
Ok(record)
},
);
Ok(iter)
}
pub fn deserialize_feature_collection_str_to_vec<'de, T>(
feature_collection_str: &str,
) -> Result<Vec<T>>
where
T: Deserialize<'de>,
{
let feature_collection_reader = feature_collection_str.as_bytes();
deserialize_feature_collection(feature_collection_reader)?.collect()
}
pub fn deserialize_feature_collection_to_vec<'de, T>(
feature_collection_reader: impl Read,
) -> Result<Vec<T>>
where
T: Deserialize<'de>,
{
deserialize_feature_collection(feature_collection_reader)?.collect()
}
#[cfg_attr(feature = "geo-types", doc = "```")]
#[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
pub fn deserialize_geometry<'de, D, G>(deserializer: D) -> std::result::Result<G, D::Error>
where
D: Deserializer<'de>,
G: TryFrom<crate::Geometry>,
G::Error: std::fmt::Display,
{
let geojson_geometry = crate::Geometry::deserialize(deserializer)?;
geojson_geometry
.try_into()
.map_err(deserialize_error_msg::<D>)
}
#[cfg_attr(feature = "geo-types", doc = "```")]
#[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
pub fn deserialize_optional_geometry<'de, D, G>(
deserializer: D,
) -> std::result::Result<Option<G>, D::Error>
where
D: Deserializer<'de>,
G: TryFrom<crate::Geometry>,
G::Error: std::fmt::Display,
{
Option::<crate::Geometry>::deserialize(deserializer)?
.map(TryInto::try_into)
.transpose()
.map_err(deserialize_error_msg::<D>)
}
fn deserialize_error_msg<'de, D: Deserializer<'de>>(
error: impl std::fmt::Display,
) -> <D as serde::Deserializer<'de>>::Error {
Error::custom(format!(
"unable to convert from geojson Geometry: {}",
error
))
}
pub fn deserialize_features_from_feature_collection(
feature_collection_reader: impl Read,
) -> impl Iterator<Item = Result<Feature>> {
FeatureReader::from_reader(feature_collection_reader).features()
}
#[cfg_attr(feature = "geo-types", doc = "```")]
#[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
pub fn deserialize_single_feature<'de, T>(feature_reader: impl Read) -> Result<T>
where
T: Deserialize<'de>,
{
let feature_value: JsonValue = serde_json::from_reader(feature_reader)?;
let deserializer = feature_value.into_deserializer();
let visitor = FeatureVisitor::new();
Ok(deserializer.deserialize_map(visitor)?)
}
#[cfg_attr(feature = "geo-types", doc = "```")]
#[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
pub fn from_feature<'de, T>(feature: Feature) -> Result<T>
where
T: Deserialize<'de>,
{
let feature_value: JsonValue = serde_json::to_value(feature)?;
let deserializer = feature_value.into_deserializer();
let visitor = FeatureVisitor::new();
Ok(deserializer.deserialize_map(visitor)?)
}
struct FeatureVisitor<D> {
_marker: PhantomData<D>,
}
impl<D> FeatureVisitor<D> {
fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
impl<'de, D> serde::de::Visitor<'de> for FeatureVisitor<D>
where
D: Deserialize<'de>,
{
type Value = D;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
write!(formatter, "a valid GeoJSON Feature object")
}
fn visit_map<A>(self, mut map_access: A) -> std::result::Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut has_feature_type = false;
use std::collections::HashMap;
let mut hash_map: HashMap<String, JsonValue> = HashMap::new();
while let Some((key, value)) = map_access.next_entry::<String, JsonValue>()? {
if key == "type" {
if value.as_str() == Some("Feature") {
has_feature_type = true;
} else {
return Err(Error::custom(
"GeoJSON Feature had a `type` other than \"Feature\"",
));
}
} else if key == "geometry" {
if let JsonValue::Object(_) = value {
hash_map.insert("geometry".to_string(), value);
} else {
return Err(Error::custom(
"GeoJSON Feature had an unexpected `geometry`",
));
}
} else if key == "id" {
match &value {
JsonValue::String(_) | JsonValue::Number(_) | JsonValue::Null => {
hash_map.insert("id".to_string(), value);
}
_ => {
return Err(Error::custom("GeoJSON Feature had an unexpected `id`"));
}
}
} else if key == "properties" {
if let JsonValue::Object(properties) = value {
for (prop_key, prop_value) in properties {
hash_map.insert(prop_key, prop_value);
}
} else {
return Err(Error::custom("GeoJSON Feature had unexpected `properties`"));
}
} else {
log::debug!("foreign members are not handled by Feature deserializer")
}
}
if has_feature_type {
let d2 = hash_map.into_deserializer();
let result =
Deserialize::deserialize(d2).map_err(|e| Error::custom(format!("{}", e)))?;
Ok(result)
} else {
Err(Error::custom(
"A GeoJSON Feature must have a `type: \"Feature\"` field, but found none.",
))
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::{JsonObject, JsonValue};
use serde_json::json;
pub(crate) fn feature_collection() -> JsonValue {
json!({
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": "Dinagat Islands",
"age": 123
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [2.3, 4.5]
},
"properties": {
"name": "Neverland",
"age": 456
}
}
]
})
}
#[test]
fn test_deserialize_feature_collection() {
use crate::Feature;
let feature_collection_string = feature_collection().to_string();
let bytes_reader = feature_collection_string.as_bytes();
let records: Vec<Feature> = deserialize_features_from_feature_collection(bytes_reader)
.map(|feature_result: Result<Feature>| feature_result.unwrap())
.collect();
assert_eq!(records.len(), 2);
let first_age = {
let props = records.first().unwrap().properties.as_ref().unwrap();
props.get("age").unwrap().as_i64().unwrap()
};
assert_eq!(first_age, 123);
let second_age = {
let props = records.get(1).unwrap().properties.as_ref().unwrap();
props.get("age").unwrap().as_i64().unwrap()
};
assert_eq!(second_age, 456);
}
#[cfg(feature = "geo-types")]
mod geo_types_tests {
use super::*;
use serde::Deserialize;
#[test]
fn geometry_field() {
#[derive(Deserialize)]
struct MyStruct {
#[serde(deserialize_with = "deserialize_geometry")]
geometry: geo_types::Geometry<f64>,
name: String,
age: u64,
}
let feature_collection_string = feature_collection().to_string();
let bytes_reader = feature_collection_string.as_bytes();
let records: Vec<MyStruct> = deserialize_feature_collection(bytes_reader)
.expect("a valid feature collection")
.collect::<Result<Vec<_>>>()
.expect("valid features");
assert_eq!(records.len(), 2);
assert_eq!(
records[0].geometry,
geo_types::point!(x: 125.6, y: 10.1).into()
);
assert_eq!(records[0].name, "Dinagat Islands");
assert_eq!(records[0].age, 123);
assert_eq!(
records[1].geometry,
geo_types::point!(x: 2.3, y: 4.5).into()
);
assert_eq!(records[1].name, "Neverland");
assert_eq!(records[1].age, 456);
}
#[test]
fn specific_geometry_variant_field() {
#[derive(Deserialize)]
struct MyStruct {
#[serde(deserialize_with = "deserialize_geometry")]
geometry: geo_types::Point<f64>,
name: String,
age: u64,
}
let feature_collection_string = feature_collection().to_string();
let bytes_reader = feature_collection_string.as_bytes();
let records: Vec<MyStruct> = deserialize_feature_collection(bytes_reader)
.expect("a valid feature collection")
.collect::<Result<Vec<_>>>()
.expect("valid features");
assert_eq!(records.len(), 2);
assert_eq!(records[0].geometry, geo_types::point!(x: 125.6, y: 10.1));
assert_eq!(records[0].name, "Dinagat Islands");
assert_eq!(records[0].age, 123);
assert_eq!(records[1].geometry, geo_types::point!(x: 2.3, y: 4.5));
assert_eq!(records[1].name, "Neverland");
assert_eq!(records[1].age, 456);
}
#[test]
fn wrong_geometry_variant_field() {
#[allow(unused)]
#[derive(Deserialize)]
struct MyStruct {
#[serde(deserialize_with = "deserialize_geometry")]
geometry: geo_types::LineString<f64>,
name: String,
age: u64,
}
let feature_collection_string = feature_collection().to_string();
let bytes_reader = feature_collection_string.as_bytes();
let records: Vec<Result<MyStruct>> = deserialize_feature_collection(bytes_reader)
.unwrap()
.collect();
assert_eq!(records.len(), 2);
assert!(records[0].is_err());
assert!(records[1].is_err());
let err = match records[0].as_ref() {
Ok(_ok) => panic!("expected Err, but found OK"),
Err(e) => e,
};
let expected_err_text = r#"Error while deserializing GeoJSON: unable to convert from geojson Geometry: Expected type: `LineString`, but found `Point`"#;
assert_eq!(err.to_string(), expected_err_text);
}
#[test]
fn deserializes_optional_point() {
#[derive(serde::Deserialize)]
struct MyStruct {
#[serde(rename = "count")]
_count: usize,
#[serde(default, deserialize_with = "deserialize_optional_geometry")]
geometry: Option<geo_types::Point<f64>>,
}
let json = json! {{
"count": 0,
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
}};
let feature: MyStruct = serde_json::from_value(json).unwrap();
assert!(feature.geometry.is_some());
let json = json! {{
"count": 1,
}};
let feature: MyStruct = serde_json::from_value(json).unwrap();
assert!(feature.geometry.is_none())
}
mod id_field {
use super::*;
#[test]
fn string_id() {
#[derive(Deserialize)]
struct MyStruct {
#[serde(deserialize_with = "deserialize_geometry")]
geometry: geo_types::Point<f64>,
name: String,
age: u64,
id: String,
}
let feature_string = json!({
"type": "Feature",
"id": "my-id-123",
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": "Dinagat Islands",
"age": 123
}
})
.to_string();
let my_struct: MyStruct = deserialize_single_feature(feature_string.as_bytes())
.expect("a valid feature collection");
assert_eq!(my_struct.geometry, geo_types::point!(x: 125.6, y: 10.1));
assert_eq!(my_struct.id, "my-id-123");
assert_eq!(my_struct.name, "Dinagat Islands");
assert_eq!(my_struct.age, 123);
}
#[test]
fn numeric_id() {
#[derive(Deserialize)]
struct MyStruct {
#[serde(deserialize_with = "deserialize_geometry")]
geometry: geo_types::Point<f64>,
name: String,
age: u64,
id: u64,
}
let feature_string = json!({
"type": "Feature",
"id": 123,
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": "Dinagat Islands",
"age": 222
}
})
.to_string();
let my_struct: MyStruct = deserialize_single_feature(feature_string.as_bytes())
.expect("a valid feature collection");
assert_eq!(my_struct.geometry, geo_types::point!(x: 125.6, y: 10.1));
assert_eq!(my_struct.id, 123);
assert_eq!(my_struct.name, "Dinagat Islands");
assert_eq!(my_struct.age, 222);
}
#[test]
fn optional_id() {
#[allow(unused)]
#[derive(Deserialize)]
struct MyStruct {
#[serde(deserialize_with = "deserialize_geometry")]
geometry: geo_types::Point<f64>,
name: String,
age: u64,
id: Option<u64>,
}
let feature_string = json!({
"type": "Feature",
"id": 123,
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": "Dinagat Islands",
"age": 222
}
})
.to_string();
let my_struct: MyStruct = deserialize_single_feature(feature_string.as_bytes())
.expect("a valid feature collection");
assert_eq!(my_struct.id, Some(123));
let feature_string = json!({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": "Dinagat Islands",
"age": 222
}
})
.to_string();
let my_struct: MyStruct = deserialize_single_feature(feature_string.as_bytes())
.expect("a valid feature collection");
assert_eq!(my_struct.id, None);
let feature_string = json!({
"type": "Feature",
"id": null,
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": "Dinagat Islands",
"age": 222
}
})
.to_string();
let my_struct: MyStruct = deserialize_single_feature(feature_string.as_bytes())
.expect("a valid feature collection");
assert_eq!(my_struct.id, None);
}
}
#[test]
fn test_from_feature() {
#[derive(Debug, PartialEq, Deserialize)]
struct MyStruct {
#[serde(deserialize_with = "deserialize_geometry")]
geometry: geo_types::Point<f64>,
name: String,
age: u64,
}
let feature = Feature {
bbox: None,
geometry: Some(crate::Geometry::new_point([125.6, 10.1])),
id: None,
properties: Some(
json!({
"name": "Dinagat Islands",
"age": 123,
})
.as_object()
.unwrap()
.clone(),
),
foreign_members: None,
};
let actual: MyStruct = from_feature(feature).unwrap();
let expected = MyStruct {
geometry: geo_types::Point::new(125.6, 10.1),
name: "Dinagat Islands".to_string(),
age: 123,
};
assert_eq!(actual, expected);
}
}
#[cfg(feature = "geo-types")]
#[test]
fn roundtrip() {
use crate::ser::serialize_geometry;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct MyStruct {
#[serde(
serialize_with = "serialize_geometry",
deserialize_with = "deserialize_geometry"
)]
geometry: geo_types::Point<f64>,
name: String,
age: u64,
}
let feature_collection_string = feature_collection().to_string();
let bytes_reader = feature_collection_string.as_bytes();
let mut elements = deserialize_feature_collection_to_vec::<MyStruct>(bytes_reader).unwrap();
for element in &mut elements {
element.age += 1;
element.geometry.set_x(element.geometry.x() + 1.0);
}
let actual_output = crate::ser::to_feature_collection_string(&elements).unwrap();
use std::str::FromStr;
let actual_output_json = JsonValue::from_str(&actual_output).unwrap();
let expected_output_json = json!({
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [126.6, 10.1]
},
"properties": {
"name": "Dinagat Islands",
"age": 124
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [3.3, 4.5]
},
"properties": {
"name": "Neverland",
"age": 457
}
}
]
});
assert_eq!(actual_output_json, expected_output_json);
}
#[cfg(feature = "geo-types")]
#[test]
fn all_props() {
use crate::ser::serialize_geometry;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct MyStruct {
#[serde(
serialize_with = "serialize_geometry",
deserialize_with = "deserialize_geometry"
)]
geometry: geo_types::Point<f64>,
#[serde(flatten)]
properties: JsonObject,
}
let feature_collection_string = feature_collection().to_string();
let bytes_reader = feature_collection_string.as_bytes();
let mut elements = deserialize_feature_collection_to_vec::<MyStruct>(bytes_reader).unwrap();
for element in &mut elements {
let mut age = element
.properties
.get("age")
.expect("key to exist")
.as_u64()
.expect("numeric");
age += 1;
*element.properties.get_mut("age").expect("key to exist") = age.into();
element.geometry.set_x(element.geometry.x() + 1.0);
}
let actual_output = crate::ser::to_feature_collection_string(&elements).unwrap();
use std::str::FromStr;
let actual_output_json = JsonValue::from_str(&actual_output).unwrap();
let expected_output_json = json!({
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [126.6, 10.1]
},
"properties": {
"name": "Dinagat Islands",
"age": 124
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [3.3, 4.5]
},
"properties": {
"name": "Neverland",
"age": 457
}
}
]
});
assert_eq!(actual_output_json, expected_output_json);
}
}