Skip to content
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

Way to update Yojson.Basic.json member? #54

Open
sgrove opened this issue Mar 12, 2018 · 7 comments
Open

Way to update Yojson.Basic.json member? #54

sgrove opened this issue Mar 12, 2018 · 7 comments

Comments

@sgrove
Copy link

sgrove commented Mar 12, 2018

If I have a json object, is there a function that takes a member, a function of Yojson.Basic.member -> Yojson.Basic.member, and returns a new json object with an updated value at the key?

I'm looking for something similar to OCaml's with, specifically because I need to update a list of a key to shorten it to a sublist, then pass the whole structure to another function.

@mjambon
Copy link
Member

mjambon commented Mar 13, 2018

No, there isn't such thing. We could add it, but we'd also need to add similar functions for mutating data, with consistent naming. Hopefully we won't end up embedding a full javascript implementation.

@seanpoulter
Copy link

seanpoulter commented Nov 26, 2018

I'm brand new to OCaml but would be willing to have a try at this. Do you know any examples of these update functions @mjambon? I've found one StackOverflow post so far. Oooh! 🎉 There's also Map.update.

@seanpoulter
Copy link

seanpoulter commented Nov 27, 2018

I've been hacking away on this tonight to transfer over the Map.update syntax to update a JSON object as an `Assoc (string : json) list. Any thoughts? Is this anywhere near what you're expecting? The functions to add or remove could be based on this? 🤔

--

Edit: The provided code did not add the member if it wasn't in the JSON object. 👎I've removed the incorrect suggested code.

@seanpoulter
Copy link

I've made some progress! 🎉

Here's a function that's uses the same syntax as Map.update (docs). The API isn't particularly friendly but it provides the ability to add, update, and remove a member. That means it's easy to derive simpler add and remove functions with signatures like:

val add : (key: string) -> (v': json) -> (json: json) -> (json': json)
val remove : (key: string) -> (json: json) -> (json': json)

Here's what I'd propose for update:

update key f json returns a json with the same bindings as json except for the member of a JSON object with the name key. The member will be added, removed, or updated depending on the value of y where y is f (Some v) or f None.

If a member, (k, v), is found with where k = key, then f is called with f (Some v). When y is Some v' and v is not physically equal to v' the member will be updated. When y is None the member is removed.

If a member with k = key is not found, f is called with f None. When y is Some v then the member will be added at the end of the list as (key, v).

let update key f (json : Yojson.Basic.json) =
    let rec update_json_obj = function
        | [] ->
            begin match f None with
                | None -> []
                | Some v -> [(key, v)]
            end
        | ((k, v) as m) :: tl ->
            if k = key then
                match f (Some v) with
                | None -> update_json_obj tl
                | Some v' ->
                    if v' == v then m :: tl
                    else (k, v') :: tl
            else m :: (update_json_obj tl)
    in

    match json with
    | `Assoc obj -> `Assoc (update_json_obj obj)
    | _ -> json
;;

with the following test cases (evalated in utop):

(* No change - New value, add None *)
Yojson.Basic.from_string "{}" |> update "a" (fun _ -> None);;

(* Add value - Empty list *)
Yojson.Basic.from_string "{}" |> update "a" (fun _ -> Some (`Int 1));;

(* Add value - Append to end of list*)
Yojson.Basic.from_string "{a:1}" |> update "b" (fun _ -> Some (`Int 2));;

(* Update value - Change *)
Yojson.Basic.from_string "{a:1}" |> update "a" (fun _ -> Some (`String "one"));;

(* Update value - No change when physical equality *)
let values = `Assoc [("a", `Int 1); ("b", `Int 2)] in
let json = `Assoc [("values", values)] in
let json' = json |> update "values" (fun _ -> Some (values)) in
(Yojson.Basic.Util.member "values") json == (Yojson.Basic.Util.member "values" json');;

(* Remove *)
Yojson.Basic.from_string "{a:1}" |> update "a" (fun _ -> None);;

and the trivial implementation for add and remove:

let add k v = update k (fun _ -> Some v)
let remove k = update k (fun _ -> None)

@seanpoulter
Copy link

As I mentioned before, I don't like the API but it's "familiar". Would you want a PR with these changes? Any pointers on how things are tested in the repo (or OCaml)?

@sgrove
Copy link
Author

sgrove commented Feb 15, 2019 via email

@NathanReb
Copy link
Collaborator

It seems like there's a demand for such functions so I suggest you open a PR with what you have so far and we can discuss the details there.

Is that fine by you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants