-
Notifications
You must be signed in to change notification settings - Fork 160
feat: add JsonPointer support and conversions from and to json #2763
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
Closed
Closed
Changes from 3 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
08b930f
feat: add JsonPointer support and conversions from and to json
Erchiusx 77da681
bugfix: export suberror JsonPointerParsingError
Erchiusx b5270e6
chore: run `moon info` afterwards
Erchiusx 4ac0cd0
move Bytes from builtin to bytes package
bobzhang 80bc285
tweak
bobzhang 3eb11dd
tweak bytes Show output
bobzhang 49b6942
promote
bobzhang c9a1a97
ci: setup for copilot
peter-jerry-ye 5bc5c2f
refactor: use fixedarray to improve decode
Copilot 9c255ca
refactor: use fixedarray to improve decode_lossy
Copilot 70f33c0
fix: extract ch - 0x10000
tonyfettes c5e3132
fix: extract ch - 0x10000
tonyfettes a6b7107
Implement Compare trait for @list.List with lexicographic order
Copilot 629e505
chore: comments illustrating implementations of JsonPointer; bugfixs
Erchiusx 0828e82
chore: format code
Erchiusx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,277 @@ | ||
| ///| | ||
| pub enum JsonPointer { | ||
| RootPointer | ||
| Pointer(JsonPointer, token~ : String) | ||
| } derive(Show) | ||
|
|
||
| ///| | ||
| pub(all) enum PointerComponentInterpretion { | ||
| ArrayIndex(Int) | ||
| ObjectIndex(String) | ||
| } derive(Show) | ||
|
|
||
| ///| | ||
| fn interprete(component : String) -> PointerComponentInterpretion { | ||
| if component.length() == 0 { | ||
| return ObjectIndex(component) | ||
| } | ||
| match component[0].to_char() { | ||
| Some('1'..<'9') => | ||
| match (try? @strconv.parse_int(component, base=10)) { | ||
| Err(_) => ObjectIndex(component) | ||
| Ok(i) => ArrayIndex(i) | ||
| } | ||
| Some('0') => | ||
| if component.length() == 1 { | ||
| ArrayIndex(0) | ||
| } else { | ||
| ObjectIndex(component) | ||
| } | ||
| _ => ObjectIndex(component) | ||
| } | ||
| } | ||
|
|
||
| ///| | ||
| pub fn JsonPointer::apply(self : JsonPointer, value : Json) -> Json? { | ||
| match (self, value) { | ||
| (RootPointer, _) => Some(value) | ||
| (Pointer(p, token~), v) => | ||
| match p.apply(v) { | ||
| Some(Object(parent)) => Some(parent[token]) | ||
| Some(Array(parent)) => | ||
| if interprete(token) is ArrayIndex(i) { | ||
| Some(parent[i]) | ||
| } else { | ||
| None | ||
| } | ||
| _ => None | ||
| } | ||
| } | ||
| } | ||
|
|
||
| ///| | ||
| pub impl Div for JsonPointer with div(parent, child) { | ||
| match child { | ||
| RootPointer => parent | ||
| Pointer(p, token~) => Pointer(parent / p, token~) | ||
| } | ||
| } | ||
|
|
||
| ///| | ||
| fn escape_component(token : String) -> String { | ||
| String::from_iter( | ||
| Iter::new(yield_ => for c in token { | ||
| match c { | ||
| '/' => { | ||
| if yield_('~') is IterContinue { | ||
| if yield_('1') is IterContinue { | ||
| continue | ||
| } | ||
| } | ||
| break IterEnd | ||
| } | ||
| '~' => { | ||
| if yield_('~') is IterContinue { | ||
| if yield_('0') is IterContinue { | ||
| continue | ||
| } | ||
| } | ||
| break IterEnd | ||
| } | ||
| _ => if yield_(c) is IterEnd { break IterEnd } | ||
| } | ||
| } else { | ||
| IterEnd | ||
| }), | ||
| ) | ||
| } | ||
|
|
||
| ///| | ||
| pub(all) suberror JsonPointerParsingError (String, String) derive ( | ||
| Eq, | ||
| Show, | ||
| ToJson, | ||
| ) | ||
|
|
||
| ///| | ||
| fn get_unescaped(p : Int, full : String) -> Char raise JsonPointerParsingError { | ||
| match full[p] { | ||
| '0' => '~' | ||
| '1' => '/' | ||
| _ => | ||
| raise JsonPointerParsingError( | ||
| ( | ||
| full, | ||
| ( | ||
| #|Parsing JsonPointer error: | ||
| $| the escaping sequence ~\{full[p].to_char().unwrap()} is not found | ||
| $| Note: at position \{p} | ||
| ), | ||
| ), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| ///| | ||
| fn unescape_component(token : String) -> String raise JsonPointerParsingError { | ||
| let mut escaping : Bool = false | ||
| let res : Array[Char] = [] | ||
| for k, v in token { | ||
| if escaping { | ||
| let unescaped = get_unescaped(k, token) | ||
| res.push(unescaped) | ||
| escaping = false | ||
| } else { | ||
| match v { | ||
| '~' => escaping = true | ||
| _ => res.push(v) | ||
| } | ||
| } | ||
| } | ||
| String::from_array(res) | ||
| } | ||
|
|
||
| ///| | ||
| pub fn JsonPointer::iter(self : Self) -> Iter[String] { | ||
| Iter::new(yield_ => match self { | ||
| RootPointer => IterEnd | ||
| Pointer(p, token~) => | ||
| for component in p { | ||
| guard yield_(component) is IterContinue else { break IterEnd } | ||
| } else { | ||
| if yield_(token) is IterContinue { | ||
| IterEnd | ||
| } else { | ||
| IterEnd | ||
| } | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| ///| | ||
| // pub impl Show for JsonPointer with output(self, logger) { | ||
| // match self { | ||
| // RootPointer => logger.write_char('/') | ||
| // Pointer(p, token~) => | ||
| // logger..write_object(p)..write_string(escape_component(token)) | ||
| // } | ||
| // } | ||
|
|
||
| ///| | ||
| fn JsonPointer::from_iter( | ||
| iter : Iter[String], | ||
| ) -> JsonPointer raise JsonPointerParsingError { | ||
| let mut res = RootPointer | ||
| for component in iter { | ||
| let token = unescape_component(component) | ||
| res = Pointer(res, token~) | ||
| } | ||
| res | ||
| } | ||
|
|
||
| ///| | ||
| pub impl FromJson for JsonPointer with from_json(json, _) { | ||
| match json { | ||
| String(str) => { | ||
| let preparedError = JsonDecodeError( | ||
| ( | ||
| Root, | ||
| "error parsing JsonPointer's json string: invalid format\n\tjson: \{str}", | ||
| ), | ||
| ) | ||
| if str != "" && str[0] != '/' { | ||
| raise preparedError | ||
| } | ||
| let components = str.split("/")[1:].map(c => c.to_string()) | ||
| let result = try? JsonPointer::from_iter(components) | ||
| match result { | ||
| Ok(pointer) => pointer | ||
| Err(_) => raise preparedError | ||
| } | ||
| } | ||
| _ => | ||
| raise JsonDecodeError( | ||
| (Root, "JsonPointer can only be constructed from a json string"), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| ///| | ||
| pub impl ToJson for JsonPointer with to_json(self) { | ||
| match self { | ||
| RootPointer => Json::String("") | ||
| Pointer(_) => { | ||
| let mut res = "" | ||
| for component in self { | ||
| res += "/" + escape_component(component) | ||
| } | ||
| Json::string(res) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| ///| | ||
| /// using the following test suite present in rfc | ||
| /// { | ||
| /// "foo": ["bar", "baz"], | ||
| /// "": 0, | ||
| /// "a/b": 1, | ||
| /// "c%d": 2, | ||
| /// "e^f": 3, | ||
| /// "g|h": 4, | ||
| /// "i\\j": 5, | ||
| /// "k\"l": 6, | ||
| /// " ": 7, | ||
| /// "m~n": 8 | ||
| /// } | ||
| /// | ||
| /// queries and results: | ||
| /// "" // the whole document | ||
| /// "/foo" ["bar", "baz"] | ||
| /// "/foo/0" "bar" | ||
| /// "/" 0 | ||
| /// "/a~1b" 1 | ||
| /// "/c%d" 2 | ||
| /// "/e^f" 3 | ||
| /// "/g|h" 4 | ||
| /// "/i\\j" 5 | ||
| /// "/k\"l" 6 | ||
| /// "/ " 7 | ||
| /// "/m~0n" 8 | ||
| test "json-pointer basic test" { | ||
| let json = Object({ | ||
| "foo": Array([String("bar"), String("baz")]), | ||
| "": Json::number(0), | ||
| "a/b": Json::number(1), | ||
| "c%d": Json::number(2), | ||
| "e^f": Json::number(3), | ||
| "g|h": Json::number(4), | ||
| "i\\j": Json::number(5), | ||
| "k\"l": Json::number(6), | ||
| " ": Json::number(7), | ||
| "m~n": Json::number(8), | ||
| }) | ||
| let queries = [ | ||
| "", "/foo", "/foo/0", "/", "/a~1b", "/c%d", "/e^f", "/g|h", "/i\\j", "/k\"l", | ||
| "/ ", "/m~0n", | ||
| ] | ||
| let pointers = queries | ||
| .map(Json::string) | ||
| .map(json => JsonPointer::from_json(json, Root)) | ||
| let results = pointers.map(pointer => pointer.apply(json)) | ||
| inspect(pointers, content=queries.to_json()) | ||
| inspect( | ||
| results, | ||
| content=[json, ["bar", "baz"].to_json(), "bar", 0, 1, 2, 3, 4, 5, 6, 7, 8] | ||
| .map(a => Some(a)) | ||
| .to_json(), | ||
| ) | ||
| } | ||
|
|
||
| ///| | ||
| test "json-pointer div-operator" { | ||
| inspect( | ||
| Pointer(RootPointer, token="def") / Pointer(RootPointer, token="abc"), | ||
| content=Pointer(Pointer(RootPointer, token="def"), token="abc").to_json(), | ||
| ) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be a loop at least?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
loop needs intermediate result collection, I think the cost is generally alike;
but it is true that implementations need improvement
I will use iterator to implement this apply, and I am considering to reverse the link direction as a refactor.