Editing the JSON tree
Closed this issue ยท 14 comments
Hey, I've recently found this library, thank you for the reasonable design and nice maintenance.
As far as I understand, the displayed JSON tree is read-only. Do you have plans to enable editing the JSON tree? If not, do you maybe have any estimates about the magnitude of effort that implementation would require?
I think that support for changing field names and values or adding/removing array items or object fields could be really useful. With editing enabled, this library could be easily used as a generic UI for any serde::De/Serialize type without the need of any derive macros. I've seen egui_struct, enum2egui, egui_inspect and others but they are much more limited in possibilities and code quality.
Hi, you are correct that the JSON tree visualisation is currently read-only.
I do not have plans for this library to introduce an explicit feature for editing JSON - I would prefer to keep this library more generic and flexible.
However, what I could do is support custom rendering callbacks. This way, you could, for example, register a custom callback to render a text-edit field in place of a value from the JSON, and then modify the JSON value after rendering. The editing UI and conversion of input to JSON values would be down to the user, so as to be more customisable and not tie this library to any specific JSON type.
I can start work on this soon.
Thank you for the quick answer. So far I was simply considering possibilities, I'm still not sure which direction makes sense in my use case.
Based on what you've described, it might be easy to render a custom editor for any existing value, but adding/removing array items or object fields still sounds out of scope with the approach you've described, right?
adding/removing array items or object fields still sounds out of scope with the approach you've described, right?
I think that should still be possible with the approach I am envisaging.
For example, in a render callback/hook, you could render an "x" button after each object/array element. If the button is pressed, you could push some kind of "mutation event" to a vec in the enclosing scope, which captures the JSON pointer string, and that the event is deletion of an object field. In the enclosing scope, after the JSON tree has been shown, you could loop over the "mutation events" and apply the mutations to the JSON value that you were showing. So you could remove an object field at the pointer string, you could remove an array element at some index, you could update a string to a new value, etc. How you actually modify the JSON value is down to the user and the specific JSON type being used.
@izolyomi I have made some progress:
Screen.Recording.2024-06-10.at.19.07.38.mov
This is using a custom "render value" hook for rendering text edit boxes, and a custom "post render value" hook to show a button after each value.
Is this along the lines of what you had in mind?
Wow, thanks for the enthusiasm, this is awesome already. ;-)
I'm sorry for the delayed answer. The video above shows updating field values and removing fields exactly how I imagined. The only thing I still miss is the possibility of somehow adding new array items or object fields. It could probably be done with a similar hook adding a "+" button. Would that also be straightforward to include?
Yes exactly - this should be possible.
I will include a comprehensive example to demonstrate the render hooks feature through the editing use case once it's finished. Basically the render hook will be called with a struct that contains a reference to the nested JSON value (if applicable) and a JSON pointer, so you know the path to the value/key being rendered, and can do what you want, knowing the type of the value and it's location in the top level JSON being rendered.
I'm currently trying to decide on an API decision - exposing a single on_render hook which gets called with an enum whose variants correspond to each render event (render value, post render value, render key, post render key, etc.) or exposing separate hooks for each event individually. I am leaning towards the former since it could be simpler for the user. Stay tuned!
I agree that the former version with a single on_render hook and strongly typed event enum variants sound both safer and more flexible. On the user side, handling many different callbacks also seems to be inferior to simply writing a single, very flexible match statement.
I am nearing completion on the new on_render hook functionality in #21.
I have updated the example to include a "+" button for adding to arrays/objects. To be honest, I think a right-click context menu might be clearer for this - I find it a bit confusing to follow what array/object is being updated, so I may update this. But you can see that it is possible:
Screen.Recording.2024-06-17.at.21.33.15.mov
Equally, the "+" button could go in a different position - the on_render hook provides this flexibility.
I will merge that PR soon, and then update the docs before the next publish.
Awesome, this is really getting close now.
I agree that the "+" button could go to a different place, it probably would be more intuitive as a "dummy last item" in each array/object. If it can be easily overridden already then anyone can customize to their own needs.
Trying the latest version of the demo I've found that deleting any existing array/object item resets keys/values of all newly added items to defaults. Checking the source code I suspect that this might be related context pointers somehow being invalidated while mutations are processed. I'm not sure though, I'm not really familiar with this codebase yet.
Yes - the current edit use case demo is not fully robust yet. Adding/removing to an array/object is currently instantaneous, but existing value edits are applied only when "Save" is clicked. So, e.g. when an array element is deleted, the indices of the elements after it each decrement by 1, and those would then show the value edit state from the previous element instead of what it was edited to before the deletion occurred. But it's still a WIP.
It'd probably be better to separate additions/deletions from editing values, so those operations cannot interleave - indeed, another tool I have found appears to suffer from a similar problem! ๐ https://10015.io/tools/json-tree-viewer
I have updated the demo to use right-click context menus for performing edit operations. I am happy with this approach, and will proceed with it. I think the UI is a lot easier to follow and manage this way.
Screen.Recording.2024-06-22.at.19.43.19.mov
I have updated the demo to use right-click context menus for performing edit operations. I am happy with this approach, and will proceed with it. I think the UI is a lot easier to follow and manage this way.
Screen.Recording.2024-06-22.at.19.43.19.mov
Cool, this looks perfect. I agree that it's just as intuitive for users as the previous UI approach. It's probably also a lot simpler at the implementation side without the need for arrays of edit events and structural tree modifications to follow.
Thank you for all the efforts!
Great! Thanks for the original suggestion and feedback along the way!