Skip to content

Commit 085b8e0

Browse files
authored
Implement Serialize without going through JsonObject (#233)
* Implement `Serialize` without going through JsonObject As described in <#152>, serialization by constructing a JsonObject has to essentially clone all data, which requires extra allocations. After this change, we pass data directly to the `Serializer`. `JsonObject` and `JsonValue` conversion impls were rewritten to delegate to `Serialize`. This requires some unfortunate `panic`s where we expect `Serialize` to produce a JSON object, but I think it's better than duplicating the serialization logic. * Serialize None properties as null * Add comments around to_value unwraps and panics
1 parent 4cfbf3a commit 085b8e0

File tree

5 files changed

+140
-76
lines changed

5 files changed

+140
-76
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
* Added conversion from `Vec<Feature>` to `GeoJson`.
6+
* Changed `Serialize` impls to avoid creating intermediate `JsonObject`s.
67

78
## 0.24.1
89

src/feature.rs

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ use std::str::FromStr;
1818
use crate::errors::{Error, Result};
1919
use crate::{util, Feature, Geometry, Value};
2020
use crate::{JsonObject, JsonValue};
21-
use serde::{Deserialize, Deserializer, Serialize, Serializer};
22-
use serde_json::json;
21+
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
2322

2423
impl From<Geometry> for Feature {
2524
fn from(geom: Geometry) -> Feature {
@@ -55,35 +54,18 @@ impl FromStr for Feature {
5554

5655
impl<'a> From<&'a Feature> for JsonObject {
5756
fn from(feature: &'a Feature) -> JsonObject {
58-
let mut map = JsonObject::new();
59-
map.insert(String::from("type"), json!("Feature"));
60-
map.insert(
61-
String::from("geometry"),
62-
serde_json::to_value(&feature.geometry).unwrap(),
63-
);
64-
if let Some(ref properties) = feature.properties {
65-
map.insert(
66-
String::from("properties"),
67-
serde_json::to_value(properties).unwrap(),
68-
);
69-
} else {
70-
map.insert(
71-
String::from("properties"),
72-
serde_json::to_value(Some(serde_json::Map::new())).unwrap(),
73-
);
74-
}
75-
if let Some(ref bbox) = feature.bbox {
76-
map.insert(String::from("bbox"), serde_json::to_value(bbox).unwrap());
77-
}
78-
if let Some(ref id) = feature.id {
79-
map.insert(String::from("id"), serde_json::to_value(id).unwrap());
80-
}
81-
if let Some(ref foreign_members) = feature.foreign_members {
82-
for (key, value) in foreign_members {
83-
map.insert(key.to_owned(), value.to_owned());
57+
// The unwrap() should never panic, because Feature contains only JSON-serializable types
58+
match serde_json::to_value(feature).unwrap() {
59+
serde_json::Value::Object(obj) => obj,
60+
value => {
61+
// Panic should never happen, because `impl Serialize for Feature` always produces an
62+
// Object
63+
panic!(
64+
"serializing Feature should result in an Object, but got something {:?}",
65+
value
66+
)
8467
}
8568
}
86-
map
8769
}
8870
}
8971

@@ -182,7 +164,22 @@ impl Serialize for Feature {
182164
where
183165
S: Serializer,
184166
{
185-
JsonObject::from(self).serialize(serializer)
167+
let mut map = serializer.serialize_map(None)?;
168+
map.serialize_entry("type", "Feature")?;
169+
map.serialize_entry("geometry", &self.geometry)?;
170+
map.serialize_entry("properties", &self.properties)?;
171+
if let Some(ref bbox) = self.bbox {
172+
map.serialize_entry("bbox", bbox)?;
173+
}
174+
if let Some(ref id) = self.id {
175+
map.serialize_entry("id", id)?;
176+
}
177+
if let Some(ref foreign_members) = self.foreign_members {
178+
for (key, value) in foreign_members {
179+
map.serialize_entry(key, value)?;
180+
}
181+
}
182+
map.end()
186183
}
187184
}
188185

@@ -229,8 +226,7 @@ mod tests {
229226
use std::str::FromStr;
230227

231228
fn feature_json_str() -> &'static str {
232-
"{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"properties\":{},\"type\":\
233-
\"Feature\"}"
229+
"{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{}}"
234230
}
235231

236232
fn properties() -> Option<JsonObject> {
@@ -314,8 +310,7 @@ mod tests {
314310
#[test]
315311
fn test_display_feature() {
316312
let f = feature().to_string();
317-
assert_eq!(f, "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"properties\":{},\"type\":\
318-
\"Feature\"}");
313+
assert_eq!(f, "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{}}");
319314
}
320315

321316
#[test]
@@ -344,7 +339,7 @@ mod tests {
344339

345340
#[test]
346341
fn encode_decode_feature_with_id_number() {
347-
let feature_json_str = "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"id\":0,\"properties\":{},\"type\":\"Feature\"}";
342+
let feature_json_str = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{},\"id\":0}";
348343
let feature = crate::Feature {
349344
geometry: Some(Geometry {
350345
value: Value::Point(vec![1.1, 2.1]),
@@ -370,7 +365,7 @@ mod tests {
370365

371366
#[test]
372367
fn encode_decode_feature_with_id_string() {
373-
let feature_json_str = "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"id\":\"foo\",\"properties\":{},\"type\":\"Feature\"}";
368+
let feature_json_str = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{},\"id\":\"foo\"}";
374369
let feature = crate::Feature {
375370
geometry: Some(Geometry {
376371
value: Value::Point(vec![1.1, 2.1]),
@@ -416,7 +411,8 @@ mod tests {
416411
fn encode_decode_feature_with_foreign_member() {
417412
use crate::JsonObject;
418413
use serde_json;
419-
let feature_json_str = "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"other_member\":\"some_value\",\"properties\":{},\"type\":\"Feature\"}";
414+
let feature_json_str = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{},\"other_member\":\"some_value\"}";
415+
420416
let mut foreign_members = JsonObject::new();
421417
foreign_members.insert(
422418
String::from("other_member"),
@@ -445,6 +441,29 @@ mod tests {
445441
assert_eq!(decoded_feature, feature);
446442
}
447443

444+
#[test]
445+
fn encode_decode_feature_with_null_properties() {
446+
let feature_json_str = r#"{"type":"Feature","geometry":{"type":"Point","coordinates":[1.1,2.1]},"properties":null}"#;
447+
448+
let feature = crate::Feature {
449+
geometry: Some(Value::Point(vec![1.1, 2.1]).into()),
450+
properties: None,
451+
bbox: None,
452+
id: None,
453+
foreign_members: None,
454+
};
455+
// Test encode
456+
let json_string = encode(&feature);
457+
assert_eq!(json_string, feature_json_str);
458+
459+
// Test decode
460+
let decoded_feature = match decode(feature_json_str.into()) {
461+
GeoJson::Feature(f) => f,
462+
_ => unreachable!(),
463+
};
464+
assert_eq!(decoded_feature, feature);
465+
}
466+
448467
#[test]
449468
fn feature_ergonomic_property_access() {
450469
use serde_json::json;

src/feature_collection.rs

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
use serde::ser::SerializeMap;
1516
use std::convert::TryFrom;
1617
use std::iter::FromIterator;
1718
use std::str::FromStr;
@@ -20,7 +21,6 @@ use crate::errors::{Error, Result};
2021
use crate::{util, Bbox, Feature};
2122
use crate::{JsonObject, JsonValue};
2223
use serde::{Deserialize, Deserializer, Serialize, Serializer};
23-
use serde_json::json;
2424

2525
/// Feature Collection Objects
2626
///
@@ -44,7 +44,7 @@ use serde_json::json;
4444
///
4545
/// assert_eq!(
4646
/// serialized,
47-
/// "{\"features\":[],\"type\":\"FeatureCollection\"}"
47+
/// "{\"type\":\"FeatureCollection\",\"features\":[]}"
4848
/// );
4949
/// ```
5050
///
@@ -94,24 +94,15 @@ impl<'a> IntoIterator for &'a FeatureCollection {
9494

9595
impl<'a> From<&'a FeatureCollection> for JsonObject {
9696
fn from(fc: &'a FeatureCollection) -> JsonObject {
97-
let mut map = JsonObject::new();
98-
map.insert(String::from("type"), json!("FeatureCollection"));
99-
map.insert(
100-
String::from("features"),
101-
serde_json::to_value(&fc.features).unwrap(),
102-
);
103-
104-
if let Some(ref bbox) = fc.bbox {
105-
map.insert(String::from("bbox"), serde_json::to_value(bbox).unwrap());
106-
}
107-
108-
if let Some(ref foreign_members) = fc.foreign_members {
109-
for (key, value) in foreign_members {
110-
map.insert(key.to_owned(), value.to_owned());
97+
// The unwrap() should never panic, because FeatureCollection contains only JSON-serializable types
98+
match serde_json::to_value(fc).unwrap() {
99+
serde_json::Value::Object(obj) => obj,
100+
value => {
101+
// Panic should never happen, because `impl Serialize for FeatureCollection` always produces an
102+
// Object
103+
panic!("serializing FeatureCollection should result in an Object, but got something {:?}", value)
111104
}
112105
}
113-
114-
map
115106
}
116107
}
117108

@@ -168,7 +159,21 @@ impl Serialize for FeatureCollection {
168159
where
169160
S: Serializer,
170161
{
171-
JsonObject::from(self).serialize(serializer)
162+
let mut map = serializer.serialize_map(None)?;
163+
map.serialize_entry("type", "FeatureCollection")?;
164+
map.serialize_entry("features", &self.features)?;
165+
166+
if let Some(ref bbox) = self.bbox {
167+
map.serialize_entry("bbox", bbox)?;
168+
}
169+
170+
if let Some(ref foreign_members) = self.foreign_members {
171+
for (key, value) in foreign_members {
172+
map.serialize_entry(key, value)?;
173+
}
174+
}
175+
176+
map.end()
172177
}
173178
}
174179

src/geojson.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,11 @@ impl Serialize for GeoJson {
301301
where
302302
S: Serializer,
303303
{
304-
JsonObject::from(self).serialize(serializer)
304+
match self {
305+
GeoJson::Geometry(ref geometry) => geometry.serialize(serializer),
306+
GeoJson::Feature(ref feature) => feature.serialize(serializer),
307+
GeoJson::FeatureCollection(ref fc) => fc.serialize(serializer),
308+
}
305309
}
306310
}
307311

0 commit comments

Comments
 (0)