geojson/
ser.rs

1//!
2//! To output your struct to GeoJSON, either as a String, bytes, or to a file, your type *must*
3//! implement or derive [`serde::Serialize`]:
4//!
5//! ```rust, ignore
6//! #[derive(serde::Serialize)]
7//! struct MyStruct {
8//!     ...
9//! }
10//! ```
11//!
12//! Your type *must* have a field called `geometry` and it must be `serialize_with` [`serialize_geometry`](crate::ser::serialize_geometry):
13//!  ```rust, ignore
14//! #[derive(serde::Serialize)]
15//! struct MyStruct {
16//!     #[serde(serialize_with = "geojson::ser::serialize_geometry")]
17//!     geometry: geo_types::Point<f64>,
18//!     ...
19//! }
20//! ```
21//!
22//! All fields in your struct other than `geometry` will be serialized as `properties` of the
23//! GeoJSON Feature.
24//!
25//! # Examples
26#![cfg_attr(feature = "geo-types", doc = "```")]
27#![cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
28//! use serde::Serialize;
29//! use geojson::ser::serialize_geometry;
30//!
31//! #[derive(Serialize)]
32//! struct MyStruct {
33//!     // Serialize as geojson, rather than using the type's default serialization
34//!     #[serde(serialize_with = "serialize_geometry")]
35//!     geometry: geo_types::Point<f64>,
36//!     name: String,
37//!     population: u64
38//! }
39//!
40//! let my_structs = vec![
41//!     MyStruct {
42//!         geometry: geo_types::Point::new(11.1, 22.2),
43//!         name: "Downtown".to_string(),
44//!         population: 123
45//!     },
46//!     MyStruct {
47//!         geometry: geo_types::Point::new(33.3, 44.4),
48//!         name: "Uptown".to_string(),
49//!         population: 456
50//!     }
51//! ];
52//!
53//! let output_geojson = geojson::ser::to_feature_collection_string(&my_structs).unwrap();
54//!
55//! let expected_geojson = serde_json::json!(
56//!     {
57//!         "type":"FeatureCollection",
58//!         "features": [
59//!             {
60//!                 "type": "Feature",
61//!                 "geometry": { "coordinates": [11.1,22.2], "type": "Point" },
62//!                 "properties": {
63//!                     "name": "Downtown",
64//!                     "population": 123
65//!                 }
66//!             },
67//!             {
68//!                 "type": "Feature",
69//!                 "geometry": { "coordinates": [33.3, 44.4], "type": "Point" },
70//!                 "properties": {
71//!                     "name": "Uptown",
72//!                     "population": 456
73//!                 }
74//!             }
75//!         ]
76//!     }
77//! );
78//! #
79//! # // re-parse the json to do a structural comparison, rather than worry about formatting
80//! # // or other meaningless deviations in an exact String comparison.
81//! # let output_geojson: serde_json::Value = serde_json::from_str(&output_geojson).unwrap();
82//! #
83//! # assert_eq!(output_geojson, expected_geojson);
84//! ```
85//!
86//! # Reading *and* Writing GeoJSON
87//!
88//! This module is only concerned with Writing out GeoJSON. If you'd also like to read GeoJSON,
89//! you'll want to combine this with the functionality from the [`crate::de`] module:
90//! ```ignore
91//! #[derive(serde::Serialize, serde::Deserialize)]
92//! struct MyStruct {
93//!     // Serialize as GeoJSON, rather than using the type's default serialization
94//!     #[serde(serialize_with = "serialize_geometry", deserialize_with = "deserialize_geometry")]
95//!     geometry: geo_types::Point<f64>,
96//!     ...
97//! }
98//! ```
99use crate::{Feature, JsonObject, JsonValue, Result};
100
101use serde::{ser::Error, Serialize, Serializer};
102
103use crate::util::expect_owned_object;
104use std::convert::TryFrom;
105use std::{convert::TryInto, io};
106
107/// Serialize a single data structure to a GeoJSON Feature string.
108///
109/// Note that `T` must have a column called `geometry`.
110///
111/// See [`to_feature_collection_string`] if instead you'd like to serialize multiple features to a
112/// FeatureCollection.
113///
114/// # Errors
115///
116/// Serialization can fail if `T`'s implementation of `Serialize` decides to
117/// fail, or if `T` contains a map with non-string keys.
118pub fn to_feature_string<T>(value: &T) -> Result<String>
119where
120    T: Serialize,
121{
122    let vec = to_feature_byte_vec(value)?;
123    let string = unsafe {
124        // We do not emit invalid UTF-8.
125        String::from_utf8_unchecked(vec)
126    };
127    Ok(string)
128}
129
130/// Serialize elements to a GeoJSON FeatureCollection string.
131///
132/// Note that `T` must have a column called `geometry`.
133///
134/// # Errors
135///
136/// Serialization can fail if `T`'s implementation of `Serialize` decides to
137/// fail, or if `T` contains a map with non-string keys.
138pub fn to_feature_collection_string<T>(values: &[T]) -> Result<String>
139where
140    T: Serialize,
141{
142    let vec = to_feature_collection_byte_vec(values)?;
143    let string = unsafe {
144        // We do not emit invalid UTF-8.
145        String::from_utf8_unchecked(vec)
146    };
147    Ok(string)
148}
149
150/// Serialize a single data structure to a GeoJSON Feature byte vector.
151///
152/// Note that `T` must have a column called `geometry`.
153///
154/// # Errors
155///
156/// Serialization can fail if `T`'s implementation of `Serialize` decides to
157/// fail, or if `T` contains a map with non-string keys.
158pub fn to_feature_byte_vec<T>(value: &T) -> Result<Vec<u8>>
159where
160    T: Serialize,
161{
162    let mut writer = Vec::with_capacity(128);
163    to_feature_writer(&mut writer, value)?;
164    Ok(writer)
165}
166
167/// Serialize elements to a GeoJSON FeatureCollection byte vector.
168///
169/// Note that `T` must have a column called `geometry`.
170///
171/// # Errors
172///
173/// Serialization can fail if `T`'s implementation of `Serialize` decides to
174/// fail, or if `T` contains a map with non-string keys.
175pub fn to_feature_collection_byte_vec<T>(values: &[T]) -> Result<Vec<u8>>
176where
177    T: Serialize,
178{
179    let mut writer = Vec::with_capacity(128);
180    to_feature_collection_writer(&mut writer, values)?;
181    Ok(writer)
182}
183
184/// Serialize a single data structure as a GeoJSON Feature into the IO stream.
185///
186/// Note that `T` must have a column called `geometry`.
187///
188/// # Errors
189///
190/// Serialization can fail if `T`'s implementation of `Serialize` decides to
191/// fail, or if `T` contains a map with non-string keys.
192pub fn to_feature_writer<W, T>(writer: W, value: &T) -> Result<()>
193where
194    W: io::Write,
195    T: Serialize,
196{
197    let feature_serializer = FeatureWrapper::new(value);
198    let mut serializer = serde_json::Serializer::new(writer);
199    feature_serializer.serialize(&mut serializer)?;
200    Ok(())
201}
202
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.
241pub fn to_feature<T>(value: T) -> Result<Feature>
242where
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
263/// Serialize elements as a GeoJSON FeatureCollection into the IO stream.
264///
265/// Note that `T` must have a column called `geometry`.
266///
267/// # Errors
268///
269/// Serialization can fail if `T`'s implementation of `Serialize` decides to
270/// fail, or if `T` contains a map with non-string keys.
271pub fn to_feature_collection_writer<W, T>(writer: W, features: &[T]) -> Result<()>
272where
273    W: io::Write,
274    T: Serialize,
275{
276    use serde::ser::SerializeMap;
277
278    let mut ser = serde_json::Serializer::new(writer);
279    let mut map = ser.serialize_map(Some(2))?;
280    map.serialize_entry("type", "FeatureCollection")?;
281    map.serialize_entry("features", &Features::new(features))?;
282    map.end()?;
283    Ok(())
284}
285
286/// [`serde::serialize_with`](https://serde.rs/field-attrs.html#serialize_with) helper to serialize a type like a
287/// [`geo_types`], as a GeoJSON Geometry.
288///
289/// # Examples
290#[cfg_attr(feature = "geo-types", doc = "```")]
291#[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
292/// use serde::Serialize;
293/// use geojson::ser::serialize_geometry;
294///
295/// #[derive(Serialize)]
296/// struct MyStruct {
297///     // Serialize as geojson, rather than using the type's default serialization
298///     #[serde(serialize_with = "serialize_geometry")]
299///     geometry: geo_types::Point<f64>,
300///     name: String,
301/// }
302///
303/// let my_structs = vec![
304///     MyStruct {
305///         geometry: geo_types::Point::new(11.1, 22.2),
306///         name: "Downtown".to_string()
307///     },
308///     MyStruct {
309///         geometry: geo_types::Point::new(33.3, 44.4),
310///         name: "Uptown".to_string()
311///     }
312/// ];
313///
314/// let geojson_string = geojson::ser::to_feature_collection_string(&my_structs).unwrap();
315///
316/// assert!(geojson_string.contains(r#""geometry":{"coordinates":[11.1,22.2],"type":"Point"}"#));
317/// ```
318pub fn serialize_geometry<IG, S>(geometry: IG, ser: S) -> std::result::Result<S::Ok, S::Error>
319where
320    IG: TryInto<crate::Geometry>,
321    S: serde::Serializer,
322    <IG as TryInto<crate::Geometry>>::Error: std::fmt::Display,
323{
324    geometry
325        .try_into()
326        .map_err(serialize_error_msg::<S>)?
327        .serialize(ser)
328}
329
330/// [`serde::serialize_with`](https://serde.rs/field-attrs.html#serialize_with) helper to serialize an optional type like a
331/// [`geo_types`], as an optional GeoJSON Geometry.
332///
333/// # Examples
334#[cfg_attr(feature = "geo-types", doc = "```")]
335#[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
336/// use geojson::ser::serialize_optional_geometry;
337/// use serde::Serialize;
338/// use serde_json::{json, to_value};
339///
340/// #[derive(Serialize)]
341/// struct MyStruct {
342///     count: usize,
343///     #[serde(
344///         skip_serializing_if = "Option::is_none",
345///         serialize_with = "serialize_optional_geometry"
346///     )]
347///     geometry: Option<geo_types::Point<f64>>,
348/// }
349///
350/// let my_struct = MyStruct {
351///     count: 0,
352///     geometry: Some(geo_types::Point::new(1.2, 0.5)),
353/// };
354/// let json = json! {{
355///     "count": 0,
356///     "geometry": {
357///         "type": "Point",
358///         "coordinates": [1.2, 0.5]
359///     },
360/// }};
361/// assert_eq!(json, to_value(my_struct).unwrap());
362///
363/// let my_struct = MyStruct {
364///     count: 1,
365///     geometry: None,
366/// };
367/// let json = json! {{
368///     "count": 1,
369/// }};
370/// assert_eq!(json, to_value(my_struct).unwrap());
371/// ```
372pub fn serialize_optional_geometry<'a, IG, S>(
373    geometry: &'a Option<IG>,
374    ser: S,
375) -> std::result::Result<S::Ok, S::Error>
376where
377    &'a IG: std::convert::TryInto<crate::Geometry>,
378    S: serde::Serializer,
379    <&'a IG as TryInto<crate::Geometry>>::Error: std::fmt::Display,
380{
381    geometry
382        .as_ref()
383        .map(TryInto::try_into)
384        .transpose()
385        .map_err(serialize_error_msg::<S>)?
386        .serialize(ser)
387}
388
389fn serialize_error_msg<S: Serializer>(error: impl std::fmt::Display) -> S::Error {
390    Error::custom(format!("failed to convert geometry to GeoJSON: {}", error))
391}
392
393struct Features<'a, T>
394where
395    T: Serialize,
396{
397    features: &'a [T],
398}
399
400impl<'a, T> Features<'a, T>
401where
402    T: Serialize,
403{
404    fn new(features: &'a [T]) -> Self {
405        Self { features }
406    }
407}
408
409impl<T> serde::Serialize for Features<'_, T>
410where
411    T: Serialize,
412{
413    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
414    where
415        S: Serializer,
416    {
417        use serde::ser::SerializeSeq;
418        let mut seq = serializer.serialize_seq(None)?;
419        for feature in self.features.iter() {
420            seq.serialize_element(&FeatureWrapper::new(feature))?;
421        }
422        seq.end()
423    }
424}
425
426struct FeatureWrapper<'t, T> {
427    feature: &'t T,
428}
429
430impl<'t, T> FeatureWrapper<'t, T> {
431    fn new(feature: &'t T) -> Self {
432        Self { feature }
433    }
434}
435
436impl<T> Serialize for FeatureWrapper<'_, T>
437where
438    T: Serialize,
439{
440    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
441    where
442        S: Serializer,
443    {
444        let mut json_object: JsonObject = {
445            let value = serde_json::to_value(self.feature).map_err(|e| {
446                S::Error::custom(format!("Feature was not serializable as JSON - {}", e))
447            })?;
448            match value {
449                JsonValue::Object(object) => object,
450                JsonValue::Null => {
451                    return Err(S::Error::custom("expected JSON object but found `null`"))
452                }
453                JsonValue::Bool(_) => {
454                    return Err(S::Error::custom("expected JSON object but found `bool`"))
455                }
456                JsonValue::Number(_) => {
457                    return Err(S::Error::custom("expected JSON object but found `number`"))
458                }
459                JsonValue::String(_) => {
460                    return Err(S::Error::custom("expected JSON object but found `string`"))
461                }
462                JsonValue::Array(_) => {
463                    return Err(S::Error::custom("expected JSON object but found `array`"))
464                }
465            }
466        };
467
468        if !json_object.contains_key("geometry") {
469            // Currently it's *required* that the struct's geometry field be named `geometry`.
470            //
471            // A likely failure case for users is naming it anything else, e.g. `point: geo::Point`.
472            //
473            // We could just silently blunder on and set `geometry` to None in that case, but
474            // printing a specific error message seems more likely to be helpful.
475            return Err(S::Error::custom("missing `geometry` field"));
476        }
477        let geometry = json_object.remove("geometry");
478
479        use serde::ser::SerializeMap;
480        let mut map = serializer.serialize_map(Some(3))?;
481        map.serialize_entry("type", "Feature")?;
482        map.serialize_entry("geometry", &geometry)?;
483        map.serialize_entry("properties", &json_object)?;
484        map.end()
485    }
486}
487
488#[cfg(test)]
489mod tests {
490    use super::*;
491    use crate::JsonValue;
492
493    use serde_json::json;
494
495    use std::str::FromStr;
496
497    #[test]
498    fn happy_path() {
499        #[derive(Serialize)]
500        struct MyStruct {
501            geometry: crate::Geometry,
502            name: String,
503        }
504
505        let my_feature = {
506            let geometry = crate::Geometry::new(crate::Value::Point(vec![0.0, 1.0]));
507            let name = "burbs".to_string();
508            MyStruct { geometry, name }
509        };
510
511        let expected_output_json = json!({
512            "type": "Feature",
513            "geometry": {
514                "coordinates":[0.0,1.0],
515                "type":"Point"
516            },
517            "properties": {
518                "name": "burbs"
519            }
520        });
521
522        let actual_output = to_feature_string(&my_feature).unwrap();
523        let actual_output_json = JsonValue::from_str(&actual_output).unwrap();
524        assert_eq!(actual_output_json, expected_output_json);
525    }
526
527    mod optional_geometry {
528        use super::*;
529        #[derive(Serialize)]
530        struct MyStruct {
531            geometry: Option<crate::Geometry>,
532            name: String,
533        }
534
535        #[test]
536        fn with_some_geom() {
537            let my_feature = {
538                let geometry = Some(crate::Geometry::new(crate::Value::Point(vec![0.0, 1.0])));
539                let name = "burbs".to_string();
540                MyStruct { geometry, name }
541            };
542
543            let expected_output_json = json!({
544                "type": "Feature",
545                "geometry": {
546                    "coordinates":[0.0,1.0],
547                    "type":"Point"
548                },
549                "properties": {
550                    "name": "burbs"
551                }
552            });
553
554            let actual_output = to_feature_string(&my_feature).unwrap();
555            let actual_output_json = JsonValue::from_str(&actual_output).unwrap();
556            assert_eq!(actual_output_json, expected_output_json);
557        }
558
559        #[test]
560        fn with_none_geom() {
561            let my_feature = {
562                let geometry = None;
563                let name = "burbs".to_string();
564                MyStruct { geometry, name }
565            };
566
567            let expected_output_json = json!({
568                "type": "Feature",
569                "geometry": null,
570                "properties": {
571                    "name": "burbs"
572                }
573            });
574
575            let actual_output = to_feature_string(&my_feature).unwrap();
576            let actual_output_json = JsonValue::from_str(&actual_output).unwrap();
577            assert_eq!(actual_output_json, expected_output_json);
578        }
579
580        #[test]
581        fn without_geom_field() {
582            #[derive(Serialize)]
583            struct MyStructWithoutGeom {
584                // geometry: Option<crate::Geometry>,
585                name: String,
586            }
587            let my_feature = {
588                let name = "burbs".to_string();
589                MyStructWithoutGeom { name }
590            };
591
592            let actual_output = to_feature_string(&my_feature).unwrap_err();
593            let error_message = actual_output.to_string();
594
595            // BRITTLE: we'll need to update this test if the error message changes.
596            assert!(error_message.contains("missing"));
597            assert!(error_message.contains("geometry"));
598        }
599
600        #[test]
601        fn serializes_whatever_geometry() {
602            #[derive(Serialize)]
603            struct MyStructWithWeirdGeom {
604                // This isn't a valid geometry representation, but we don't really have a way to "validate" it
605                // so serde will serialize whatever. This test exists just to document current behavior
606                // not that it's exactly desirable.
607                geometry: Vec<u32>,
608                name: String,
609            }
610            let my_feature = {
611                let geometry = vec![1, 2, 3];
612                let name = "burbs".to_string();
613                MyStructWithWeirdGeom { geometry, name }
614            };
615
616            let expected_output_json = json!({
617                "type": "Feature",
618                "geometry": [1, 2, 3],
619                "properties": {
620                    "name": "burbs"
621                }
622            });
623
624            let actual_output = to_feature_string(&my_feature).unwrap();
625            let actual_output_json = JsonValue::from_str(&actual_output).unwrap();
626            assert_eq!(actual_output_json, expected_output_json);
627        }
628    }
629
630    #[cfg(feature = "geo-types")]
631    mod geo_types_tests {
632        use super::*;
633        use crate::de::tests::feature_collection;
634        use crate::Geometry;
635
636        #[test]
637        fn serializes_optional_point() {
638            #[derive(serde::Serialize)]
639            struct MyStruct {
640                count: usize,
641                #[serde(
642                    skip_serializing_if = "Option::is_none",
643                    serialize_with = "serialize_optional_geometry"
644                )]
645                geometry: Option<geo_types::Point<f64>>,
646            }
647
648            let my_struct = MyStruct {
649                count: 0,
650                geometry: Some(geo_types::Point::new(1.2, 0.5)),
651            };
652            let json = json! {{
653                "count": 0,
654                "geometry": {
655                    "type": "Point",
656                    "coordinates": [1.2, 0.5]
657                },
658            }};
659            assert_eq!(json, serde_json::to_value(my_struct).unwrap());
660
661            let my_struct = MyStruct {
662                count: 1,
663                geometry: None,
664            };
665            let json = json! {{
666                "count": 1,
667            }};
668            assert_eq!(json, serde_json::to_value(my_struct).unwrap());
669        }
670
671        #[test]
672        fn geometry_field_without_helper() {
673            #[derive(Serialize)]
674            struct MyStruct {
675                // If we forget the "serialize_with" helper, bad things happen.
676                // This test documents that:
677                //
678                // #[serde(serialize_with = "serialize_geometry")]
679                geometry: geo_types::Point<f64>,
680                name: String,
681                age: u64,
682            }
683
684            let my_struct = MyStruct {
685                geometry: geo_types::point!(x: 125.6, y: 10.1),
686                name: "Dinagat Islands".to_string(),
687                age: 123,
688            };
689
690            let expected_invalid_output = json!({
691              "type": "Feature",
692              // This isn't a valid geojson-Geometry. This behavior probably isn't desirable, but this
693              // test documents the current behavior of what happens if the users forgets "serialize_geometry"
694              "geometry": { "x": 125.6, "y": 10.1 },
695              "properties": {
696                "name": "Dinagat Islands",
697                "age": 123
698              }
699            });
700
701            // Order might vary, so re-parse to do a semantic comparison of the content.
702            let output_string = to_feature_string(&my_struct).expect("valid serialization");
703            let actual_output = JsonValue::from_str(&output_string).unwrap();
704
705            assert_eq!(actual_output, expected_invalid_output);
706        }
707
708        #[test]
709        fn geometry_field() {
710            #[derive(Serialize)]
711            struct MyStruct {
712                #[serde(serialize_with = "serialize_geometry")]
713                geometry: geo_types::Point<f64>,
714                name: String,
715                age: u64,
716            }
717
718            let my_struct = MyStruct {
719                geometry: geo_types::point!(x: 125.6, y: 10.1),
720                name: "Dinagat Islands".to_string(),
721                age: 123,
722            };
723
724            let expected_output = json!({
725              "type": "Feature",
726              "geometry": {
727                "type": "Point",
728                "coordinates": [125.6, 10.1]
729              },
730              "properties": {
731                "name": "Dinagat Islands",
732                "age": 123
733              }
734            });
735
736            // Order might vary, so re-parse to do a semantic comparison of the content.
737            let output_string = to_feature_string(&my_struct).expect("valid serialization");
738            let actual_output = JsonValue::from_str(&output_string).unwrap();
739
740            assert_eq!(actual_output, expected_output);
741        }
742
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
779        #[test]
780        fn serialize_feature_collection() {
781            #[derive(Serialize)]
782            struct MyStruct {
783                #[serde(serialize_with = "serialize_geometry")]
784                geometry: geo_types::Point<f64>,
785                name: String,
786                age: u64,
787            }
788
789            let my_structs = vec![
790                MyStruct {
791                    geometry: geo_types::point!(x: 125.6, y: 10.1),
792                    name: "Dinagat Islands".to_string(),
793                    age: 123,
794                },
795                MyStruct {
796                    geometry: geo_types::point!(x: 2.3, y: 4.5),
797                    name: "Neverland".to_string(),
798                    age: 456,
799                },
800            ];
801
802            let output_string =
803                to_feature_collection_string(&my_structs).expect("valid serialization");
804
805            // Order might vary, so re-parse to do a semantic comparison of the content.
806            let expected_output = feature_collection();
807            let actual_output = JsonValue::from_str(&output_string).unwrap();
808
809            assert_eq!(actual_output, expected_output);
810        }
811    }
812}