-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Serialize
without going through JsonObject
#233
Conversation
As described in <georust#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.
Thanks for the contribution @zyla! Could you say more and / or provide an example (ideally in the form of a test) of the kind of data that can trigger a panic using this PR? |
I don't think this can panic. It assumes that serializing a |
@@ -182,7 +159,26 @@ impl Serialize for Feature { | |||
where | |||
S: Serializer, | |||
{ | |||
JsonObject::from(self).serialize(serializer) | |||
let mut map = serializer.serialize_map(None)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to suggest passing in the length here, but serde_json
doesn't seem to use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great, thank you!
I agree with @lnicola that the panics "should never happen" in this case.
Since serde_json::to_value
will attempt to serialize any input, the API needs to be fallible, but our input will never fail because all our fields are serializable given that they are themselves JsonObjects, None (which is serializable), or other primitive types (String, Vec, etc.).
It's probably worth documenting this assumption though. It took me a minute to reason through it.
src/feature.rs
Outdated
if let Some(ref properties) = self.properties { | ||
map.serialize_entry("properties", properties)?; | ||
} else { | ||
map.serialize_entry("properties", &JsonObject::new())?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be null instead of an empty object.
I think this reflects a gap in our test coverage. Can you add it if you agree? Something like:
#[test]
fn feature_json_null_properties() {
let geojson_str = r#"{
"geometry": null,
"properties": null,
"type":"Feature"
}"#;
let geojson = geojson_str.parse::<GeoJson>().unwrap();
let GeoJson::Feature(feature) = geojson else {
panic!("unexpected geojson");
};
assert!(feature.properties.is_none());
assert_eq!(feature.to_string(), r#"{"type":"Feature","geometry":null,"properties":null}"#);
}
A Feature object has a member with the name "properties". The
value of the properties member is an object (any JSON object or a
JSON null value).
edit: updated the quote. I initially copied the wrong doc snippet. 🤦
(from https://datatracker.ietf.org/doc/html/rfc7946#section-3.2)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Changed the behavior and added a test.
Thanks for the review. Added some comments about that. |
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
andJsonValue
conversion impls were rewritten to delegate toSerialize
. This requires some unfortunatepanic
s where we expectSerialize
to produce a JSON object, but I think it's better than duplicating the serialization logic.Benchmark results (compared vs
4cfbf3a
):Custom structs still have to go through JsonObject, so there isn't as much improvement there.
Expected JSON outputs in tests had changed, because now we produce JSON object keys in order of the
serialize_entry
calls, instead ofserde_json::Map
order.CHANGES.md
if knowledge of this change could be valuable to users.