JSON pointer: Difference between revisions

m
→‎{{header|Wren}}: Changed to Wren S/H
(Added Wren)
m (→‎{{header|Wren}}: Changed to Wren S/H)
 
(7 intermediate revisions by 2 users not shown)
Line 456:
 
error: "bad/pointer" pointers must start with a slash or be the empty string
</pre>
 
=={{header|jq}}==
{{works with|jq}}
'''Also works with gojq, the Go implementation of jq.'''
 
The approach taken here is to piggy-back off jq's support for "array paths", that is,
JSON arrays of strings and integers representing paths in much the same way as JSON Pointer.
 
Note that jq's `getpath/1` cannot be used directly as it does not raise errors in the way required of JSON Pointer.
<syntaxhighlight lang="jq">
# JSON Pointer
 
# The characters '~' (%x7E) and '/' (%x2F) have special meanings in JSON Pointer,
# so '~' needs to be encoded as '~0'
# and '/' needs to be encoded as '~1'
# when these characters appear in a reference token.
 
# The JSON Pointer spec allows 0 both for indexing an array and for retrieving a key named "0".
# disambiguate($k) accordingly disambiguates $k w.r.t. `.`.
# $k should be a string or integer.
# If $k is a string and . is an object then: if has($k) then $k else null end.
# If $k is an integer and . is an array, then emit $k.
# If $k is an integer-valued string and . is an array then exit $k|tonumber.
# Otherwise emit null
def disambiguate( $k ):
if ($k|type) == "string"
then if type == "object" then $k
elif type == "array" and ($k|test("^[0-9]+$"))
then ($k|tonumber)
else null
end
elif ($k|type) == "number" and type == "array"
then $k
else null
end;
 
# $array should be an array of strings and integers.
# Emit the disambiguated array, suitable for running getpath/1.
# Emit null if disambiguation fails at any point.
def disambiguatePath($array):
. as $in
| reduce $array[] as $x ([];
if . then . as $path
| ($in | getpath($path) | disambiguate($x)) as $y
| if $y then . + [ $y ]
else null
end
else .
end);
 
# $p is an array as for getpath
def stepwisegetpath($p):
if ($p|length) == 0 then .
else $p[0] as $x
| if (type == "object" and ($x|type) == "string" and has($x))
or (type == "array" and ($x|type) == "number" and $x > -1 and $x < length)
then .[$x] | stepwisegetpath($p[1:])
else "JSON Pointer mismatch" | error
end
end;
# getjsonpointer() is like jq's getpath() but for jsonpointer pointers,
# and an error condition is raised if there is no value at the location.
def getjsonpointer($pointer):
if $pointer == "" then .
elif $pointer[:1] != "/" then "Invalid JSON Pointer: \($pointer)" | error
else . as $in
# first decode ~1, then ~0
| ($pointer | split("/") | .[1:]
| map(gsub("~1"; "/") | gsub("~0"; "~"))) as $array
| disambiguatePath($array) as $apath
| if $apath then stepwisegetpath($apath)
else "JSON Pointer disambiguation failed: \($pointer)" | error
end
end;
</syntaxhighlight>
The Task
<syntaxhighlight lang="jq">
def check($p; $result):
if getjsonpointer($p) == $result
then empty
else "whoops: \($p)"
end;
 
def checkError($p):
(try getjsonpointer($p) catch nan)
| . as $result
| if isnan then empty
else "\($p) should have raised an error but yielded \($result)"
end;
 
check(""; .), # The entire input document.
check("/"; "Rosetta"),
check("/ "; "Code"),
check("/abc"; ["is", "a"]),
check("/def/"; "programming"),
check("/g~1h"; "chrestomathy"),
check("/i~0j"; "site"),
check("/wiki/links/0"; "https://rosettacode.org/wiki/Rosetta_Code"),
check("/wiki/links/1"; "https://discord.com/channels/1011262808001880065"),
 
checkError("/wiki/links/2"),
checkError("/wiki/name"),
checkError("/no/such/thing"),
checkError("bad/pointer")
</syntaxhighlight>
{{output}}
Nothing, by design.
 
 
=={{header|Julia}}==
{{trans|JavaScript}}
<syntaxhighlight lang="julia">""" rosettacode.org/wiki/JSON_pointer """
 
struct JSON_Pointer
tokens::Vector{String}
end
JSON_Pointer(s::AbstractString) = JSON_Pointer(jp_parse(s))
 
resolve(p::JSON_Pointer, data) = reduce(getitem, p.tokens, init=data)
 
function jp_unencode_join(p::JSON_Pointer)
isempty(p.tokens) && return ""
return "/" * mapreduce(x -> replace(replace(x, "~" => "~0"), "/" => "~1"),
(s1, s2) -> s1 * "/" * s2, p.tokens)
end
 
Base.print(io::IO, p::JSON_Pointer) = print(io, jp_unencode_join(p))
 
function jp_parse(s::AbstractString)
if isempty(s)
return String[]
elseif s[begin] != '/'
error("Non-empty JSON pointers must begin with /")
else
return map(x -> replace(replace(x, "~1" => "/"), "~0" => "~"),
split(s, "/"))[begin+1:end]
end
end
 
"""
NOTE:
- to keep with the JavaScript convention, arrays are 0-based
- string primitives "have own" indices and `length`.
- Arrays have a `length` property.
- A property might exist with the value `undefined`.
- obj[1] is equivalent to obj["1"].
"""
getitem(obj::Vector, token::Integer) = obj[token+1]
getitem(obj::Vector, token::AbstractString) = obj[parse(Int, token)+1]
getitem(obj::Dict, token) = obj[token]
 
const doc = Dict(
"wiki" => Dict(
"links" => [
"https://rosettacode.org/wiki/Rosetta_Code",
"https://discord.com/channels/1011262808001880065",
],
),
"" => "Rosetta",
" " => "Code",
"g/h" => "chrestomathy",
"i~j" => "site",
"abc" => ["is", "a"],
"def" => Dict("" => "programming"),
)
 
const examples = [
"",
"/",
"/ ",
"/abc",
"/def/",
"/g~1h",
"/i~0j",
"/wiki/links/0",
"/wiki/links/1",
"/wiki/links/2",
"/wiki/name",
"/no/such/thing",
"bad/pointer",
]
 
for ex in examples
try
pointer = JSON_Pointer(ex)
result = resolve(pointer, doc)
println("{$ex} -> {$result}")
catch y
println("Error: $ex does not exist: $y")
end
end
</syntaxhighlight>{{out}}
<pre>
{} -> {Dict{String, Any}("abc" => ["is", "a"], "wiki" => Dict("links" => ["https://rosettacode.org/wiki/Rosetta_Code", "https://discord.com/channels/1011262808001880065"]), " " => "Code", "i~j" => "site", "def" => Dict(""
=> "programming"), "" => "Rosetta", "g/h" => "chrestomathy")}
{/} -> {Rosetta}
{/ } -> {Code}
{/abc} -> {["is", "a"]}
{/def/} -> {programming}
{/g~1h} -> {chrestomathy}
{/i~0j} -> {site}
{/wiki/links/0} -> {https://rosettacode.org/wiki/Rosetta_Code}
{/wiki/links/1} -> {https://discord.com/channels/1011262808001880065}
Error: /wiki/links/2 does not exist: BoundsError(["https://rosettacode.org/wiki/Rosetta_Code", "https://discord.com/channels/1011262808001880065"], (3,))
Error: /wiki/name does not exist: KeyError("name")
Error: /no/such/thing does not exist: KeyError("no")
Error: bad/pointer does not exist: ErrorException("Non-empty JSON pointers must begin with /")
</pre>
 
Line 608 ⟶ 817:
 
"bad/pointer" => Error pointers must start with a slash or be the empty string
</pre>
 
=={{header|Python}}==
{{trans|JavaScript}}
<syntaxhighlight lang="python">""" rosettacode.org/wiki/JSON_pointer """
 
from functools import reduce
 
 
class JSONPointer:
""" datatracker.ietf.org/doc/html/rfc6901 """
 
def __init__(self, pstring):
""" create a JSON ponter from a string """
self.tokens = parse(pstring)
 
def resolve(self, data):
""" use the pointer on an object """
return reduce(get_item, self.tokens, data)
 
def encode(self):
""" output pointer in string form """
ret = ''
for tok in self.tokens:
ret = ret + '/' + tok.replace('~', '~0').replace('/', '~1')
return ret
 
def to_string(self):
""" output pointer in string form """
return self.encode()
 
 
def parse(pst):
""" tokenize a string for use as JSON pointer """
if pst == '':
return []
if pst[0] != '/':
raise SyntaxError('Non-empty JSON pointers must begin with /')
return [a.replace('~1', '/').replace('~0', '~') for a in pst.split('/')][1:]
 
 
def get_item(obj, token):
"""
NOTE:
- string primitives 'have own' indices and `length`.
- Arrays have a `length` property.
- A property might exist with the value `undefined`.
- obj[1] is equivalent to obj['1'].
"""
if isinstance(obj, list) and isinstance(token, str):
return obj[int(token)]
return obj[token]
 
 
if __name__ == '__main__':
 
DOC = {
'wiki': {
'links': [
'https://rosettacode.org/wiki/Rosetta_Code',
'https://discord.com/channels/1011262808001880065',
],
},
'': 'Rosetta',
' ': 'Code',
'g/h': 'chrestomathy',
'i~j': 'site',
'abc': ['is', 'a'],
'def': {'': 'programming'},
}
 
EXAMPLES = [
'',
'/',
'/ ',
'/abc',
'/def/',
'/g~1h',
'/i~0j',
'/wiki/links/0',
'/wiki/links/1',
'/wiki/links/2',
'/wiki/name',
'/no/such/thing',
'bad/pointer',
]
 
for exa in EXAMPLES:
try:
pointer = JSONPointer(exa)
result = pointer.resolve(DOC)
print(f'"{exa}" -> "{result}"')
except (SyntaxError, IndexError, KeyError) as error:
print(f'Error: {exa} does not exist: {error}')
</syntaxhighlight>{{out}}
<pre>
"" -> "{'wiki': {'links': ['https://rosettacode.org/wiki/Rosetta_Code', 'https://discord.com/channels/1011262808001880065']}, '': 'Rosetta', ' ': 'Code', 'g/h': 'chrestomathy', 'i~j': 'site', 'abc': ['is', 'a'], 'def': {'': 'programming'}}"
"/" -> "Rosetta"
"/ " -> "Code"
"/abc" -> "['is', 'a']"
"/def/" -> "programming"
"/g~1h" -> "chrestomathy"
"/i~0j" -> "site"
"/wiki/links/0" -> "https://rosettacode.org/wiki/Rosetta_Code"
"/wiki/links/1" -> "https://discord.com/channels/1011262808001880065"
Error: /wiki/links/2 does not exist: list index out of range
Error: /wiki/name does not exist: 'name'
Error: /no/such/thing does not exist: 'no'
Error: bad/pointer does not exist: Non-empty JSON pointers must begin with /
</pre>
 
=={{header|Rust}}==
<syntaxhighlight lang="rust">use serde_json::{ json, Value };
 
type JSONPointer = Vec<String>;
 
fn new_json_pointer(pstring: &str) -> Result<JSONPointer, String> {
return jp_parse(pstring);
}
 
fn resolve(p: JSONPointer, data: Value) -> Result<Value, String> {
return p.iter().fold(Ok(data), |accum, val| get_item(accum, val));
}
 
fn jp_parse(pst: &str) -> Result<Vec<String>, String> {
if pst == "" {
return Ok([].to_vec());
}
if pst.chars().nth(0).unwrap() != '/' {
return Err(String::from("Non-empty JSON pointers must begin with /"));
}
return Ok(
pst
.split("/")
.map(|s| String::from(s.replace("~1", "/").replace("~0", "~")))
.collect::<Vec<String>>()[1..]
.to_vec()
);
}
 
fn get_item<'a>(obj: Result<Value, String>, token: &str) -> Result<Value, String> {
match obj {
Err(_) => {
return obj; // propagate along
}
Ok(ob) => {
match ob {
Value::Array(arr) => {
let idx = usize::from_str_radix(token, 10);
match idx {
Err(..) => {
return Err(String::from("ParseIntErr"));
}
Ok(i) => {
if i < arr.len() {
return Ok(Value::String(arr[i].to_string()));
}
return Err(String::from(format!("Index {:?} out of range", token)));
}
}
}
Value::Object(dic) => {
if dic.contains_key(token) {
return Ok(dic[token].clone());
}
return Err(String::from(format!("Key error with {:?}", token)));
}
_ => {
return Err(String::from("Unknown object"));
}
}
}
}
}
 
fn main() {
let doc =
json!({
"wiki" : {
"links" : [
"https://rosettacode.org/wiki/Rosetta_Code",
"https://discord.com/channels/1011262808001880065",
],
},
"" : "Rosetta",
" " : "Code",
"g/h" : "chrestomathy",
"i~j" : "site",
"abc" : ["is", "a"],
"def" : {"" : "programming"},
});
let examples = [
"",
"/",
"/ ",
"/abc",
"/def/",
"/g~1h",
"/i~0j",
"/wiki/links/0",
"/wiki/links/1",
"/wiki/links/2",
"/wiki/name",
"/no/such/thing",
"bad/pointer",
];
for p in examples {
let jspointer = new_json_pointer(p);
match jspointer {
Err(error) => println!("JSON pointer creation error: {error}"),
Ok(pointer) => {
let result = resolve(pointer, doc.clone());
match result {
Ok(val) => println!("\"{p}\" -> {val}"),
Err(err) => println!("Error: {p} does not exist: {err}"),
}
}
}
}
}
</syntaxhighlight>{{out}}
<pre>
"" -> {"":"Rosetta"," ":"Code","abc":["is","a"],"def":{"":"programming"},"g/h":"chrestomathy","i~j":"site","wiki":{"links":["https://rosettacode.org/wiki/Rosetta_Code","https://discord.com/channels/1011262808001880065"]}}
"/" -> "Rosetta"
"/ " -> "Code"
"/abc" -> ["is","a"]
"/def/" -> "programming"
"/g~1h" -> "chrestomathy"
"/i~0j" -> "site"
"/wiki/links/0" -> "\"https://rosettacode.org/wiki/Rosetta_Code\""
"/wiki/links/1" -> "\"https://discord.com/channels/1011262808001880065\""
Error: /wiki/links/2 does not exist: Index "2" out of range
Error: /wiki/name does not exist: Key error with "name"
Error: /no/such/thing does not exist: Key error with "no"
JSON pointer creation error: Non-empty JSON pointers must begin with /
</pre>
 
Line 613 ⟶ 1,057:
{{libheader|Wren-json}}
{{libheader|Wren-iterate}}
<syntaxhighlight lang="pythonwren">import "./json" for JSON
import "./iterate" for Indexed
 
9,479

edits