Comments (5)
I wrote my own implementation of both JSON Patch and JSON Merge Patch a while ago: https://github.com/idubrov/json-patch
It tries to be optimal and modify Value in-place and revert changes if one of the operations fails.
from json.
The json-patch crate looks great! I am happy pointing people to the implementation there -- I don't think we need this to be provided by serde_json. Thanks all!
from json.
Copying this from IRC so it doesn't get lost:
<Cryptarchy> I have a library design quandary, if anyone would like to offer an opinion. I'm attempting to implement JSON Patch into serde_json.
<Cryptarchy> And I've got the general functionality just about done. But now I've run into the interesting circular problem that JSON Patches are JSON objects. So I'd need to convert them into something else in order to operate on them.
<Cryptarchy> I could just usefrom_str
to get serde_json::Value structs to work with, and just use the values out of the map directly, but that seems kind of a hack.
<Cryptarchy> On the other hand, deserializing JSON into an object would require higher level bits of serde, which aren't available in serde_json. Or are they? That's what I'm unsure of: how to treat the string JSON object.
From serde_json's perspective I don't think we want to treat JSON patches as strings (i.e. dealing with from_str). If we just expose a type like serde_json::Patch
and implement Serialize and Deserialize for Patch, the user's crate can handle serialization in the way that makes the most sense for them. For example they may want to embed a JSON patch into a larger struct, or deserialize from bytes instead of str, or use other generic deserialization machinery in their crate, or etc.
We can expose Patch
and we can expose a function or method to apply a Patch to a serde_json::Value
. I don't know JSON patch well enough to know what makes the most sense semantically but something like serde_json::Value::apply_patch(Patch)
or serde_json::Patch::apply_to(Value)
or serde_json::apply_patch(Patch, Value)
.
@CryptArchy does that approach address your concern about needing "higher level bits of serde"?
from json.
Yes, I think that all makes sense. I currently have a Command
enum with the different ops, which I may rename as Patch
or maybe Patch
will be a type alias for Vec<Command>
. And there's a apply_patch
method on Values (currently in a Patcher
trait) that mutates the Value
according to the op(s).
I'll look into using the general (de)serialization traits and how to implement them for the final Patch
type, that sounds like the way to go.
The trickiest part so far has been meeting the demands of atomicity required for HTTP Patch semantics. The RFC isn't totally clear if that's required by the implementation itself or if the library user should respect those semantics. I opted to implement it so the library is easier to use. If any part of the patch fails, the whole thing has no effect.
I could think of two obvious ways to do it:
- copy the Value first and if the patch fails, replace the original with the copy
- generate the inverse command and build an undo stack that can be executed to rollback
I went with the second option, though more complicated, it seems like it should be more efficient (needs testing). As a nice side benefit, you can also return the undo stuck so every application of a patch gives you the commands to revert it yourself if there's a need from a higher level.
It also creates a pleasing symmetry and flow in the execution. For example, Remove
ing a value gives ownership of that value, which is then consumed by creating an Add
command to the same path which would put the value back. Add
ing a value can replace an existing value, so sometimes the inverse of an Add
is a Replace
with the old value in it. Errors return the faulty value. Essentially, no data is ever lost, just shuffled around and it's all enforced by the ownership system. It's a pretty uniquely Rustic version of JSON Patch! I'll post the code up soon to start review, though I have a lot of cleanup to do yet, so a PR is still a little ways off.
from json.
Alright, I cleaned up things a little and pushed some code up to my fork.
https://github.com/serde-rs/json/compare/master...CryptArchy:patch?expand=1
It's very rough as I'm still iterating on the concepts, but all the ops are in and working.
I added one non-standard op _Bump
to act as the inversion of the Move
op. Because of the annoying semantics where Add
can either add or replace a value, any ops that rely on Add
-like behavior get complicated. Move
is the trickiest, as it always removes one value and then may create or replace a value. So reverting it requires either an inverted Move
(if a new value was added) or a Move
and an Add
(if an existing value was replaced).
Bump
is equivalent (and implemented as) a Replace
then an Add
. I could return a Vec or tuple of Commands from patch_move
but then you need special handling for apply_patch
and everything else to flatten it.
Currently, I'm thinking that the system could just convert _Bump
into the equivalent commands before returning to the user, so that it's just an internal convenience. Since variants can't be private, I think I'd need a second Command enum that doesn't have it or something like that. I'm not sure yet, tricky stuff!
from json.
Related Issues (20)
- impl IntoDeserializer for &RawValue? HOT 1
- Duplicated field in output
- Deserilazing `Cow<'_, serde_json::value::RawValue>` always allocates
- String keys with 0s at the front can not be converted to numbers
- Feature request: add Number::from_i64 and Number::from_u64
- Wrong positions for `InvalidUnicodeCodePoint` caused by `from_utf8`
- Cannot deserialize empty tuple variant HOT 1
- How about the Performance between `from_str` and `from_value`?
- Global configuration
- how to auto remove blank space? HOT 1
- Improve DeserializeSeed documentation
- Deserializing lone surrogate to ByteBuf fails when it's nested in an enum HOT 1
- Is there a way to implement deserialize_in_place?
- Simplify array of objects to 2d array HOT 2
- Conditional Security Risk: `to_vec()` may retain sensitive data in memory on shared systems
- Is it possible to add context to the serializer object?
- Option<Option<T>> does not roundtrip correctly HOT 1
- tests don't compile on stable Rust: std::hash::RandomState only present on nightly
- `Box<RawValue>` and `#[serde(flatten)]` don't work together for deserialization HOT 3
- Bug: untagged union fails to deserialize hashmap with usize as keys
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from json.