如何修改Prolog的json_get谓词以支持数组索引查询?
json_get to Handle JSON Arrays and Nested Paths Great question! Your current json_get works perfectly for grabbing top-level fields from JSON objects, but it doesn't know how to handle JSON arrays or nested paths (like digging into an array that's a value of an object field). Let's fix that by adding support for array indices and recursive path traversal.
First, let's rewrite the predicate to handle three key scenarios:
- Fetching a field from a JSON object
- Fetching an element from a JSON array by index
- Recursively traversing a nested path of fields and indices
Here's the modified code:
% Grab a value from a JSON object using an atom field name json_get(json_obj(JSON_obj), Field, Result) :- atom(Field), memberchk((Field, Result), JSON_obj). % Grab an element from a JSON array using a 0-based integer index json_get(json_array(JSON_arr), Index, Result) :- integer(Index), nth0(Index, JSON_arr, Result). % Recursively walk through a nested path (mix of fields and indices) json_get(JSON, [Step|Rest], Result) :- json_get(JSON, Step, Intermediate), json_get(Intermediate, Rest, Result). % Optional: Empty path returns the original JSON (useful for edge cases) json_get(JSON, [], JSON).
What Changed?
- Array Support: We added a new clause that checks if we're dealing with a
json_arrayand an integer index. We usednth0/3for 0-based indexing (so index0gets the first element,1gets the second, etc.). If you prefer 1-based indexing (where1is the first element), just swapnth0withnth1. - Nested Paths: The recursive clause lets you pass a list of steps (like
["heads", 1]) to dig into nested structures. First it grabs theheadsarray from the object, then pulls the element at index1from that array. - Avoid Ambiguity: We added checks like
atom(Field)andinteger(Index)to make sure the predicate knows whether it's dealing with an object field or an array index.
Testing It Out
Let's run your example query:
json_parse('{"nome" : "Zaphod", "heads" : ["Head1", "Head2"]}', Z), json_get(Z, ["heads", 1], R).
This should return R = "Head2" (since we're using 0-based indexing). If you wanted Head1 instead, just use index 0.
A quick note about your original working query: the behavior shifts a little with the modified code. Originally, json_get(O, ["nome"], R) would return R = ["Arthur"] (a list of results from maplist). Now it returns R = "Arthur" directly (since it treats ["nome"] as a nested path with one step). If you still want to grab multiple top-level fields as a list, just use maplist(json_get(O), ["nome", "cognome"], R) directly—it'll give you R = ["Arthur", "Dent"] just like before.
Bonus: Keep Both Behaviors
If you want to retain the original ability to fetch multiple top-level fields with a list while adding nested path support, add this extra clause:
% Handle lists of top-level fields (original behavior) json_get(JSON_obj, Fields, Results) :- is_list(Fields), forall(member(F, Fields), atom(F)), % Make sure all elements are field names maplist(json_get(JSON_obj), Fields, Results).
Now json_get(O, ["nome", "cognome"], R) returns R = ["Arthur", "Dent"], and json_get(Z, ["heads", 1], R) still gives you R = "Head2"—best of both worlds!
Content of the question originates from Stack Exchange, question author Giuseppe Giubaldo

