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

feat: JSON from and to JUON (JS URL Object Notation) #61

Merged
merged 8 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions JUON.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# JUON

JUON is short for JS URL Object Notation.
Think of it as JSON values for URLs.
That means the syntax does not use characters that are not safe to use in URLs.

It is not only inspired by JSON but designed to be easily
translatable to JSON. For that, it shares the same types:
_object_, _array_, _string_, _number_, _boolean_ and _null_.

Where possible the JSON syntax was kept in JUON.
This should help both translate but also remember the syntax for everyone
already familiar with JSON.

The key syntax differences are:

* object and arrays both use round brackets `(...)`
* strings are quoted in single upticks `'...'`
* positive numbers or positive exponents must not use `+` (omit it)
* object member names are not quoted
* object member names can only use `A-Z a-z 0-9 - . _ @`
* object member names must start with a letter or `@`
* empty array is `()`, empty object cannot be expressed (approximate with `null`)
* as strings are quoted with single upticks `"` does not need escaping
but instead `'` is escaped as `\'`

Furthermore, JUON is more "lenient" than JSON both to be more user-friendly and
to allow shorter URLs though the use of shorthands and omissions:

* `null` can be encoded as the empty string or shortened to `n`
* `true` can be shortened to `t`
* `false` can be shortened to `f`
* numbers can start with `.` (= `0.`)
* numbers can end with `.` (= `.0`)

In summary, JUON can express everything JSON can, except the empty object `{}`.
Some characters in strings obviously will need URL encoding to be allowed in URLs.

## Example
Here is a JSON example:
```json
{
"name": "Freddy",
"age": 30,
"car": null,
"addresses": [{
"street": "Elm Street",
"zip": 1428,
"city": "Springwood",
"invoice": true
}]
}
```
In JUON the equivalent Java `String` would be (formatting whitespace removed):
```
(name:'Freddy',age:30,car:null,addresses:((street:'Elm Street',zip:1428,city:'Springwood',invoice:true)))
```
This could be further shortened by using shorthands and omitting `null` values:
```
(name:'Freddy',age:30,car:,addresses:((street:'Elm Street',zip:1428,city:'Springwood',invoice:t)))
```
In a URL parameter the entire value would be URL encoded, resulting in:
```
(name:'Freddy',age:30,car:,addresses:((street:'Elm+Street',zip:1428,city:'Springwood',invoice:t)))
```
(Note: the `+` could also be `%20`)


## Specification

15 changes: 14 additions & 1 deletion src/main/java/org/hisp/dhis/jsontree/JsonNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -128,6 +127,20 @@ static JsonNode ofNonStandard( String json ) {
return JsonTree.ofNonStandard( json ).get( JsonPath.ROOT );
}

/**
* Creates a new lazily parsed {@link JsonNode} tree from special URL object notation.
* <p>
* Note that the {@link JsonNode}'s {@link JsonNode#getDeclaration()} will be the equivalent JSON, not the original
* URL notation.
*
* @param juon a value in URL notation
* @return the given URL notation input as {@link JsonNode}
* @since 1.3
*/
static JsonNode ofUrlObjectNotation(String juon) {
return of( Juon.toJson( juon ));
}

/**
* Create a new lazily parsed {@link JsonNode} tree.
*
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/hisp/dhis/jsontree/JsonPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private static String keyOf( String name, boolean forceSegment ) {
// edge special case: {...} but only opens at the start => dot works
if ( !hasDot && name.charAt( 0 ) == '{' && name.indexOf( '{', 1 ) < 0 ) return "."+name;
// special case: has curly open but no valid curly close => plain or dot works
if (indexOfInnerCurlySegmentEnd( name ) < 1) return name.charAt( 0 ) == '{' ? "."+name : name;
if (!hasDot && indexOfInnerCurlySegmentEnd( name ) < 1) return name.charAt( 0 ) == '{' ? "."+name : name;
return curlyEscapeWithCheck( name );
}

Expand Down
Loading