Skip to content

Commit 42f396f

Browse files
committed
feat (cli): Canonicalization of JSON-LD ordered by @id
1 parent 341cbdc commit 42f396f

File tree

7 files changed

+95
-5
lines changed

7 files changed

+95
-5
lines changed

docs/use/canonicalize/index.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
limitations under the License.
1717
-->
1818

19-
# Canonicalize
19+
# Canonicalize (AKA Normalize)
2020

21-
_Canonicalization_ transforms a resource into a "standard" representation.
21+
_Canonicalization,_ which is also called _normalization,_ transforms a resource into a "standard" representation.
2222

2323
This process is dependent on the type of the resource, but typically includes things such as:
2424

@@ -27,7 +27,7 @@ This process is dependent on the type of the resource, but typically includes th
2727
* Rewriting literal values
2828
* Fixed Encoding
2929

30-
This is sometimes useful e.g. when testing, to compare output to a fixed expected outcome.
30+
This is useful e.g. when testing, to compare output to a fixed expected outcome.
3131

3232
It also has an application in [cryptography](https://github.com/enola-dev/enola/issues/284), and is useful when _"signing"_ things.
3333

@@ -54,3 +54,24 @@ $ ./enola canonicalize --pretty --load=file:test/canonicalize.json
5454
```
5555

5656
Note how the order of the keys in the JSON changes, among other changes.
57+
58+
## JSON-LD
59+
60+
`enola canonicalize` for JSON-LD transforms this `canonicalize.jsonld`:
61+
62+
```json
63+
{% include "../../../test/canonicalize.jsonld" %}
64+
```
65+
66+
```bash cd ../.././..
67+
$ ./enola canonicalize --pretty --load=file:test/canonicalize.jsonld --output=file:test/canonicalize.jsonld.expected
68+
...
69+
```
70+
71+
into this - note how the 🎨 painters' order was swapped, because not just all map keys but the list itself was also ordered alphabetically by `@id`:
72+
73+
```json
74+
{% include "../../../test/canonicalize.jsonld.expected" %}
75+
```
76+
77+
Future versions may implement full [RDF Dataset Canonicalization](https://www.w3.org/TR/rdf-canon/).

java/dev/enola/cli/CanonicalizeCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
@CommandLine.Command(
3333
name = "canonicalize",
3434
description = {
35-
"Canonicalize Resources",
35+
"Canonicalize (AKA normalize) resources",
3636
"using e.g. RFC 8785 JSON Canonicalization Scheme (JCS) inspired approach",
3737
"(but this implementation is currently not yet fully compliant with that RFC)"
3838
})

java/dev/enola/common/canonicalize/Canonicalizer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333

3434
public class Canonicalizer implements ResourceConverter {
3535

36+
// TODO Implement https://www.w3.org/TR/rdf-canon/
37+
3638
public static final String PRETTY_QUERY_PARAMETER = "pretty";
3739

3840
public static void canonicalize(ReadableResource in, WritableResource out, boolean pretty)

java/dev/enola/common/canonicalize/CanonicalizerTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,22 @@ public void canonicalJSON_is_UTF8() throws IOException {
7676

7777
@Test
7878
public void rfc8785() throws IOException {
79-
var in = new ClasspathResource("canonicalize.json", MediaType.JSON_UTF_8);
79+
var in = new ClasspathResource("canonicalize.json");
8080
var out = new MemoryResource(MediaType.JSON_UTF_8);
8181
Canonicalizer.canonicalize(in, out, false);
8282

8383
var expected = new ClasspathResource("canonicalize.json.expected", MediaType.JSON_UTF_8);
8484
assertThat(out.charSource().read()).isEqualTo(expected.charSource().read());
8585
}
86+
87+
@Test
88+
public void jsonld() throws IOException {
89+
var md = MediaType.parse("application/ld+json").withCharset(UTF_8);
90+
var in = new ClasspathResource("canonicalize.jsonld", md);
91+
var out = new MemoryResource(MediaType.JSON_UTF_8);
92+
Canonicalizer.canonicalize(in, out, true);
93+
94+
var expected = new ClasspathResource("canonicalize.jsonld.expected", MediaType.JSON_UTF_8);
95+
assertThat(out.charSource().read()).isEqualTo(expected.charSource().read());
96+
}
8697
}

java/dev/enola/common/yamljson/JSON.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ public static String write(Object root, boolean format) {
5858
*/
5959
@SuppressWarnings("rawtypes")
6060
public static String canonicalize(String json, boolean format) {
61+
// TODO Consider instead using
62+
// https://github.com/filip26/titanium-json-ld/blob/5c2c02c1f65b8e885fb689a460efba3f6925b479/src/main/java/com/apicatalog/jsonld/json/JsonCanonicalizer.java#L39
6163
return write(sortByKeyIfMap(readObject(json)), format);
6264
}
6365

@@ -73,6 +75,8 @@ private static Object sortByKeyIfMap(Object object) {
7375
var sorted = sortByKeyIfMap(element);
7476
newList.add(sorted);
7577
}
78+
// TODO Do this only when canonicalizing JSON-LD, not any JSON:
79+
sortListByID(newList);
7680
return newList;
7781
}
7882
return object;
@@ -89,5 +93,21 @@ private static Map<String, Object> sortByKey(Map<String, Object> map) {
8993
return newMap.build();
9094
}
9195

96+
private static void sortListByID(List<Object> list) {
97+
Collections.sort(
98+
list,
99+
(o1, o2) -> {
100+
// skipcq: JAVA-C1003
101+
if (o1 instanceof Map m1 && o2 instanceof Map m2) {
102+
var oid1 = m1.get("@id");
103+
var oid2 = m2.get("@id");
104+
// skipcq: JAVA-C1003
105+
if (oid1 instanceof String id1 && oid2 instanceof String id2)
106+
return id1.compareTo(id2);
107+
return 0;
108+
} else return 0;
109+
});
110+
}
111+
92112
private JSON() {}
93113
}

test/canonicalize.jsonld

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[
2+
{
3+
"@id": "http://example.enola.dev/Picasso",
4+
"https://schema.org/name": [
5+
{
6+
"@value": "Pablo Picasso"
7+
}
8+
]
9+
},
10+
{
11+
"https://schema.org/name": [
12+
{
13+
"@value": "Salvador Domingo Felipe Jacinto Dalí"
14+
}
15+
],
16+
"@id": "http://example.enola.dev/Dalí"
17+
}
18+
]

test/canonicalize.jsonld.expected

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[
2+
{
3+
"@id": "http://example.enola.dev/Dalí",
4+
"https://schema.org/name": [
5+
{
6+
"@value": "Salvador Domingo Felipe Jacinto Dalí"
7+
}
8+
]
9+
},
10+
{
11+
"@id": "http://example.enola.dev/Picasso",
12+
"https://schema.org/name": [
13+
{
14+
"@value": "Pablo Picasso"
15+
}
16+
]
17+
}
18+
]

0 commit comments

Comments
 (0)