Skip to content

Commit

Permalink
Maks JsonObject API more usable
Browse files Browse the repository at this point in the history
  • Loading branch information
Gene Gleyzer committed Nov 12, 2024
1 parent 5a84542 commit 1c0274b
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 134 deletions.
17 changes: 6 additions & 11 deletions lib_json/src/main/x/json.x
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,22 @@ module json.xtclang.org
/**
* JSON types include primitive types, array types, and map types.
*/
typedef (Primitive | Map<String, Doc> | Array<Doc>) as Doc;

/**
* A type representing a JSON Object.
*/
typedef Map<String, Doc> as JsonObject;
typedef (Primitive | Map<String, Doc> | Doc[]) as Doc;

/**
* A type representing a JSON Array.
*/
typedef Array<Doc> as JsonArray;
typedef Doc[] as JsonArray;

/**
* A type representing a non-primitive JSON structure.
*/
typedef JsonObject | JsonArray as JsonStruct;
typedef Map<String, Doc> | JsonArray as JsonStruct;

/**
* @return a new instance of a mutable `JsonObject`
* @return a new instance of a mutable [JsonObject].
*/
JsonObject newObject() = new ListMap<String, Doc>();
JsonObject newObject(Map<String, Doc> map = []) = new JsonObject(map);

/**
* @return a builder that can produce immutable JSON object instances
Expand All @@ -54,7 +49,7 @@ module json.xtclang.org
/**
* @return a new instance of a mutable `JsonArray`
*/
JsonArray newArray() = new Array<Doc>();
JsonArray newArray() = new Doc[];

/**
* @return a builder that can produce immutable JSON array instances
Expand Down
6 changes: 3 additions & 3 deletions lib_json/src/main/x/json/JsonArrayBuilder.x
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class JsonArrayBuilder
/**
* A type that is a function that can create a new instance of a mutable `JsonArray`.
*/
typedef function JsonArray () as Factory;
typedef function JsonArray() as Factory;

/**
* Create a JSON array builder.
Expand All @@ -18,7 +18,7 @@ class JsonArrayBuilder
*/
construct(JsonArray? template = Null, Factory factory = () -> json.newArray()) {
this.factory = factory;
values = new Array();
values = new Doc[];
if (template.is(JsonArray)) {
values.addAll(template);
}
Expand All @@ -32,7 +32,7 @@ class JsonArrayBuilder
/**
* The array of values to be used to create a JSON array.
*/
private Array<Doc> values;
private Doc[] values;

/**
* @return the number of values that have been added to the builder.
Expand Down
35 changes: 35 additions & 0 deletions lib_json/src/main/x/json/JsonObject.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* JSON Object.
*/
@AutoFreezable
class JsonObject
implements Freezable
delegates Map<String, Doc>(jsonObject) {

construct(Map<String, Doc> map = []) {
jsonObject = new ListMap();
if (!map.empty) {
jsonObject.putAll(map);
}
}

private Map<String, Doc> jsonObject;

@Override
immutable Freezable freeze(Boolean inPlace = False) = makeImmutable();

@Override
@Op("[]") Value? getOrNull(Key key) = super(key);

@Override
@Op("[]=") void putInPlace(Key key, Value value) = super(key, value);

// this doesn't work atm, but it should; will make put ops more usable
// @Op("[]=") void putInPlace(Key key, Int value) = putInPlace(key, value.toIntLiteral());
//
// @Op("[]=") void putInPlace(Key key, FPNumber value) = putInPlace(key, value.toIntLiteral());

@Auto
Doc toDoc() = jsonObject;
}

66 changes: 17 additions & 49 deletions manualTests/src/main/x/TestSimple.x
Original file line number Diff line number Diff line change
@@ -1,63 +1,31 @@
module TestSimple {

import ecstasy.collections.NaturalHasher;
package json import json.xtclang.org;

@Inject Console console;

import json.*;

void run() {
Char a = 'a';
Char b = 'b';
console.print(a.hashCode());
console.print(new NaturalHasher<Char>().hashOf(a));

console.print(a==b);
console.print(new NaturalHasher<Char>().areEqual(a, b));

console.print(a<=>b);

try {
Hashable h = obfuscate(a);
console.print(h.hashCode());
}
catch (Exception e) {
console.print(e);
}

try {
Const c = obfuscate(a);
console.print(c.hashCode());
}
catch (Exception e) {
console.print(e);
}
Test t = new Test();
console.print(t.getObject());
console.print(t.getDoc());

try {
Orderable c1 = obfuscate(a);
Orderable c2 = obfuscate(b);
console.print(c1 <=> c2);
}
catch (Exception e) {
console.print(e);
}
}

service Test {

try {
Const c1 = obfuscate(a);
Const c2 = obfuscate(b);
console.print(c1 == c2);
}
catch (Exception e) {
console.print(e);
JsonObject getObject(Int i = 1) {
JsonObject o = json.newObject();
o["a"] = i /*this will not be needed*/.toIntLiteral();
return o; // no need to freeze
}

try {
Const c1 = obfuscate(a);
Const c2 = obfuscate(b);
console.print(c1 <=> c2);
}
catch (Exception e) {
console.print(e);
Doc getDoc(Dec d = 1) {
JsonObject o = json.newObject();
o["a"] = d/*this will not be needed*/.toFPLiteral();
return o;
}
}

<T> T obfuscate(Object o) = o.as(T);
}
6 changes: 3 additions & 3 deletions manualTests/src/main/x/json/json_test/JsonArrayTest.x
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ class JsonArrayTest {
.add(json.objectBuilder().add("one", 1).add("two", 2).add("three", 3).add("four", 4))
.add(json.objectBuilder().add("five", 5).add("six", 6))
.build();
assert array == Array<Doc>:[
Map<String, Doc>:["one"=1, "two"=2, "three"=3, "four"=4],
Map<String, Doc>:["five"=5, "six"=6]
assert array == Doc[]:[
["one"=1, "two"=2, "three"=3, "four"=4],
["five"=5, "six"=6]
];
}

Expand Down
82 changes: 41 additions & 41 deletions manualTests/src/main/x/json/json_test/JsonBuilderTest.x
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,32 @@ class JsonBuilderTest {

@Test
void shouldCopySimpleObject() {
JsonObject object = hideCompileType(Map<String, Doc>:["one"=11, "two"=22, "three"=33]);
JsonObject object = json.newObject(["one"=11, "two"=22, "three"=33]);
JsonObject result = JsonBuilder.deepCopy(object);
assertDeepCopy(object, result);
}

@Test
void shouldCopySimpleArray() {
JsonArray array = hideCompileType(Array<Doc>:[1, 2, 3]);
JsonArray array = hideCompileType([1, 2, 3]);
JsonArray result = JsonBuilder.deepCopy(array);
assertDeepCopy(array, result);
}

@Test
void shouldCopyComplexObject() {
JsonObject child = Map<String, Doc>:["one"=11, "two"=22, "three"=33];
JsonArray array = Array<Doc>:[1, 2, 3];
JsonObject object = Map<String, Doc>:["one"=child, "two"=array, "three"=33];
JsonObject child = json.newObject(["one"=11, "two"=22, "three"=33]);
JsonArray array = [1, 2, 3];
JsonObject object = json.newObject(["one"=child, "two"=array, "three"=33]);
JsonObject result = JsonBuilder.deepCopy(object);
assertDeepCopy(object, result);
}

@Test
void shouldCopyComplexArray() {
JsonArray child = Array<Doc>:[1, 2, 3];
JsonObject object = Map<String, Doc>:["one"=11, "two"=22, "three"=33];
JsonArray array = Array<Doc>:[child, object];
JsonArray child = [1, 2, 3];
JsonObject object = json.newObject(["one"=11, "two"=22, "three"=33]);
JsonArray array = [child, object];
JsonArray result = JsonBuilder.deepCopy(array);
assertDeepCopy(array, result);
}
Expand Down Expand Up @@ -94,64 +94,64 @@ class JsonBuilderTest {

@Test
void shouldMergeSimpleObjectIntoObject() {
JsonObject target = Map:["a"="b"];
JsonObject source = Map:["c"="d"];
JsonObject target = json.newObject(["a"="b"]);
JsonObject source = json.newObject(["c"="d"]);
JsonObject result = new JsonObjectBuilder(target).deepMerge(source).build();
assert result == Map:["a"="b", "c"="d"];
assert result == json.newObject(["a"="b", "c"="d"]);
}

@Test
void shouldMergeComplexObjectIntoObject() {
JsonObject target = Map:["a"="b"];
JsonObject source = Map:["c"=Map<String, Doc>:["one"="two"]];
JsonObject target = json.newObject(["a"="b"]);
JsonObject source = json.newObject(["c"=["one"="two"]]);
JsonObject result = new JsonObjectBuilder(target).deepMerge(source).build();
assert result == Map<String, Doc>:["a"="b", "c"=Map<String, Doc>:["one"="two"]];
assert result == json.newObject(["a"="b", "c"=["one"="two"]]);
}

@Test
void shouldMergeArrayIntoObject() {
JsonObject target = Map:["a"="b"];
JsonObject target = json.newObject(["a"="b"]);
JsonArray source = [1, 2, 3];
JsonObject result = new JsonObjectBuilder(target).deepMerge(source).build();
assert result == Map<String, Doc>:["a"="b", "0"=1, "1"=2, "2"=3];
assert result == json.newObject(["a"="b", "0"=1, "1"=2, "2"=3]);
}

@Test
void shouldMergeObjectWithObjectIntoObjectWithExistingObject() {
JsonObject target = Map:["a"="b", "c"=Map<String, Doc>:["one"=1, "two"=2]];
JsonObject source = Map:["c"=Map<String, Doc>:["two"=22, "three"=3]];
JsonObject target = json.newObject(["a"="b", "c"=["one"=1, "two"=2]]);
JsonObject source = json.newObject(["c"=["two"=22, "three"=3]]);
JsonObject result = new JsonObjectBuilder(target).deepMerge(source).build();
assert result == Map<String, Doc>:["a"="b", "c"=Map<String, Doc>:["one"=1, "two"=22, "three"=3]];
assert result == json.newObject(["a"="b", "c"=["one"=1, "two"=22, "three"=3]]);
}

@Test
void shouldMergeObjectWithObjectIntoObjectWithExistingArray() {
JsonObject target = Map:["a"="b", "c"=Array<Doc>:["one", "two", "three"]];
JsonObject source = Map:["c"=Map<String, Doc>:["0"="one-updated", "2"="three-updated"]];
JsonObject target = json.newObject(["a"="b", "c"=["one", "two", "three"]]);
JsonObject source = json.newObject(["c"=["0"="one-updated", "2"="three-updated"]]);
JsonObject result = new JsonObjectBuilder(target).deepMerge(source).build();
assert result == Map<String, Doc>:["a"="b", "c"=Array<Doc>:["one-updated", "two", "three-updated"]];
assert result == json.newObject(["a"="b", "c"=["one-updated", "two", "three-updated"]]);
}

@Test
void shouldMergeObjectWithObjectIntoObjectWithExistingComplexArray() {
JsonObject objOrig0 = Map<String, Doc>:["one"=1, "two"=2];
JsonObject objOrig1 = Map<String, Doc>:["three"=3, "four"=4, "five"=5];
JsonObject objOrig2 = Map<String, Doc>:["six"=6, "seven"=7];
JsonObject target = Map<String, Doc>:["a"="b", "c"=Array<Doc>:[objOrig0, objOrig1, objOrig2]];
JsonObject objUpdate1 = Map<String, Doc>:["four"=44];
JsonObject objExpected1 = Map<String, Doc>:["three"=3, "four"=44, "five"=5];
JsonObject source = Map<String, Doc>:["c"=Map<String, Doc>:["1"=objUpdate1]];
JsonObject objOrig0 = json.newObject(["one"=1, "two"=2]);
JsonObject objOrig1 = json.newObject(["three"=3, "four"=4, "five"=5]);
JsonObject objOrig2 = json.newObject(["six"=6, "seven"=7]);
JsonObject target = json.newObject(["a"="b", "c"=[objOrig0, objOrig1, objOrig2]]);
JsonObject objUpdate1 = json.newObject(["four"=44]);
JsonObject objExpected1 = json.newObject(["three"=3, "four"=44, "five"=5]);
JsonObject source = json.newObject(["c"=["1"=objUpdate1]]);
JsonObject result = new JsonObjectBuilder(target).deepMerge(source).build();
Doc c = result["c"];
assert c.is(Array<Doc>);
assert c == Array<Doc>:[objOrig0, objExpected1, objOrig2];
//assert result == Map<String, Doc>:["a"="b", "c"=Array<Doc>:[objOrig0, objExpected1, objOrig2]];
assert c.is(Doc[]);
assert c == Doc[]:[objOrig0, objExpected1, objOrig2];
//assert result == json.newObject(["a"="b", "c"=Doc[]:[objOrig0, objExpected1, objOrig2]]);
}

@Test
void shouldNotMergeObjectWithObjectWithNonIntKeysIntoObjectWithExistingArray() {
JsonObject target = Map<String, Doc>:["a"="b", "c"=Array<Doc>:["one", "two", "three"]];
JsonObject source = Map<String, Doc>:["c"=Map<String, Doc>:["0"="one-updated", "three"="three-updated"]];
JsonObject target = json.newObject(["a"="b", "c"=["one", "two", "three"]]);
JsonObject source = json.newObject(["c"=["0"="one-updated", "three"="three-updated"]]);
JsonObjectBuilder builder = new JsonObjectBuilder(target);
try {
builder.deepMerge(source);
Expand All @@ -160,13 +160,13 @@ class JsonBuilderTest {
// expected
}
// target should be unchanged
assert target == Map<String, Doc>:["a"="b", "c"=Array<Doc>:["one", "two", "three"]];
assert target == json.newObject(["a"="b", "c"=["one", "two", "three"]]);
}

@Test
void shouldNotMergeObjectWithObjectWithOutOfRangeKeysIntoObjectWithExistingArray() {
JsonObject target = Map<String, Doc>:["a"="b", "c"=Array<Doc>:["one", "two", "three"]];
JsonObject source = Map<String, Doc>:["c"=Map<String, Doc>:["0"="one-updated", "3"="four"]];
JsonObject target = json.newObject(["a"="b", "c"=["one", "two", "three"]]);
JsonObject source = json.newObject(["c"=["0"="one-updated", "3"="four"]]);
JsonObjectBuilder builder = new JsonObjectBuilder(target);
try {
builder.deepMerge(source);
Expand All @@ -175,14 +175,14 @@ class JsonBuilderTest {
// expected
}
// target should be unchanged
assert target == Map<String, Doc>:["a"="b", "c"=Array<Doc>:["one", "two", "three"]];
assert target == json.newObject(["a"="b", "c"=["one", "two", "three"]]);
}

@Test
void shouldMergeObjectWithPrimitiveIntoObjectWithExistingPrimitive() {
JsonObject target = Map:["a"="b", "c"="d"];
JsonObject source = Map:["c"="updated"];
JsonObject target = json.newObject(["a"="b", "c"="d"]);
JsonObject source = json.newObject(["c"="updated"]);
JsonObject result = new JsonObjectBuilder(target).deepMerge(source).build();
assert result == Map:["a"="b", "c"="updated"];
assert result == json.newObject(["a"="b", "c"="updated"]);
}
}
Loading

0 comments on commit 1c0274b

Please sign in to comment.