Skip to content

Commit 4381fbb

Browse files
Concept map JSON serialization (#404)
## What is the goal of this PR? We implement the JSON serialization of concept maps according to typedb/typedb-behaviour#238.
1 parent 2a1d78f commit 4381fbb

File tree

11 files changed

+285
-1
lines changed

11 files changed

+285
-1
lines changed

api/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ java_library(
3737
"@vaticle_typedb_protocol//grpc/java:typedb-protocol",
3838

3939
# External dependencies from Maven
40+
"@maven//:com_eclipsesource_minimal_json_minimal_json",
4041
"@maven//:com_google_code_findbugs_jsr305",
4142
],
4243
)

api/answer/ConceptMap.java

+9
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
package com.vaticle.typedb.client.api.answer;
2323

24+
import com.eclipsesource.json.Json;
25+
import com.eclipsesource.json.JsonObject;
2426
import com.vaticle.typedb.client.api.concept.Concept;
2527
import com.vaticle.typedb.common.collection.Pair;
2628

@@ -39,6 +41,13 @@ public interface ConceptMap {
3941
@CheckReturnValue
4042
Concept get(String variable);
4143

44+
@CheckReturnValue
45+
default JsonObject toJSON() {
46+
JsonObject object = Json.object();
47+
map().forEach((resVar, resConcept) -> object.add(resVar, resConcept.toJSON()));
48+
return object;
49+
}
50+
4251
Explainables explainables();
4352

4453
interface Explainables {

api/concept/Concept.java

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
package com.vaticle.typedb.client.api.concept;
2323

24+
import com.eclipsesource.json.JsonObject;
2425
import com.vaticle.typedb.client.api.TypeDBTransaction;
2526
import com.vaticle.typedb.client.api.concept.thing.Attribute;
2627
import com.vaticle.typedb.client.api.concept.thing.Entity;
@@ -123,6 +124,9 @@ default boolean isRelation() {
123124
@CheckReturnValue
124125
boolean isRemote();
125126

127+
@CheckReturnValue
128+
JsonObject toJSON();
129+
126130
interface Remote extends Concept {
127131

128132
void delete();

api/concept/thing/Attribute.java

+25
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,23 @@
2121

2222
package com.vaticle.typedb.client.api.concept.thing;
2323

24+
import com.eclipsesource.json.Json;
25+
import com.eclipsesource.json.JsonObject;
26+
import com.eclipsesource.json.JsonValue;
2427
import com.vaticle.typedb.client.api.TypeDBTransaction;
2528
import com.vaticle.typedb.client.api.concept.type.AttributeType;
2629
import com.vaticle.typedb.client.api.concept.type.ThingType;
30+
import com.vaticle.typedb.client.common.exception.TypeDBClientException;
2731

2832
import javax.annotation.CheckReturnValue;
2933
import java.time.LocalDateTime;
34+
import java.time.format.DateTimeFormatter;
3035
import java.util.stream.Stream;
3136

37+
import static com.vaticle.typedb.client.common.exception.ErrorMessage.Internal.ILLEGAL_STATE;
38+
3239
public interface Attribute<VALUE> extends Thing {
40+
DateTimeFormatter ISO_LOCAL_DATE_TIME_MILLIS = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");
3341

3442
@Override
3543
@CheckReturnValue
@@ -88,6 +96,23 @@ default boolean isDateTime() {
8896
@CheckReturnValue
8997
Attribute.Remote<VALUE> asRemote(TypeDBTransaction transaction);
9098

99+
@Override
100+
default JsonObject toJSON() {
101+
JsonValue value;
102+
switch (getType().getValueType()) {
103+
case BOOLEAN: value = Json.value(asBoolean().getValue()); break;
104+
case LONG: value = Json.value(asLong().getValue()); break;
105+
case DOUBLE: value = Json.value(asDouble().getValue()); break;
106+
case STRING: value = Json.value(asString().getValue()); break;
107+
case DATETIME: value = Json.value(asDateTime().getValue().format(ISO_LOCAL_DATE_TIME_MILLIS)); break;
108+
default: throw new TypeDBClientException(ILLEGAL_STATE);
109+
}
110+
return Json.object()
111+
.add("type", getType().getLabel().scopedName())
112+
.add("value_type", getType().getValueType().name().toLowerCase())
113+
.add("value", value);
114+
}
115+
91116
interface Remote<VALUE> extends Thing.Remote, Attribute<VALUE> {
92117

93118
@CheckReturnValue

api/concept/thing/Thing.java

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
package com.vaticle.typedb.client.api.concept.thing;
2323

24+
import com.eclipsesource.json.Json;
25+
import com.eclipsesource.json.JsonObject;
2426
import com.vaticle.typedb.client.api.TypeDBTransaction;
2527
import com.vaticle.typedb.client.api.concept.Concept;
2628
import com.vaticle.typedb.client.api.concept.type.AttributeType;
@@ -51,6 +53,11 @@ default boolean isThing() {
5153
@CheckReturnValue
5254
Thing.Remote asRemote(TypeDBTransaction transaction);
5355

56+
@Override
57+
default JsonObject toJSON() {
58+
return Json.object().add("type", getType().getLabel().scopedName());
59+
}
60+
5461
interface Remote extends Concept.Remote, Thing {
5562

5663
void setHas(Attribute<?> attribute);

api/concept/type/Type.java

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
package com.vaticle.typedb.client.api.concept.type;
2323

24+
import com.eclipsesource.json.Json;
25+
import com.eclipsesource.json.JsonObject;
2426
import com.vaticle.typedb.client.api.TypeDBTransaction;
2527
import com.vaticle.typedb.client.api.concept.Concept;
2628
import com.vaticle.typedb.client.common.Label;
@@ -48,6 +50,11 @@ default boolean isType() {
4850
@Override
4951
Remote asRemote(TypeDBTransaction transaction);
5052

53+
@Override
54+
default JsonObject toJSON() {
55+
return Json.object().add("label", getLabel().scopedName());
56+
}
57+
5158
interface Remote extends Type, Concept.Remote {
5259

5360
void setLabel(String label);

dependencies/vaticle/repositories.bzl

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def vaticle_typedb_behaviour():
5353
git_repository(
5454
name = "vaticle_typedb_behaviour",
5555
remote = "https://github.com/vaticle/typedb-behaviour",
56-
commit = "04ec1f75cc376524b3cac75af37e1716230845e0", # sync-marker: do not remove this comment, this is used for sync-dependencies by @vaticle_typedb_behaviour
56+
commit = "46619244d5f1773505eeacf6d5bd0ae71781f8e8" # sync-marker: do not remove this comment, this is used for sync-dependencies by @vaticle_typedb_behaviour
5757
)
5858

5959
def vaticle_factory_tracing():
+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#
2+
# Copyright (C) 2022 Vaticle
3+
#
4+
# Licensed to the Apache Software Foundation (ASF) under one
5+
# or more contributor license agreements. See the NOTICE file
6+
# distributed with this work for additional information
7+
# regarding copyright ownership. The ASF licenses this file
8+
# to you under the Apache License, Version 2.0 (the
9+
# "License"); you may not use this file except in compliance
10+
# with the License. You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing,
15+
# software distributed under the License is distributed on an
16+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
# KIND, either express or implied. See the License for the
18+
# specific language governing permissions and limitations
19+
# under the License.
20+
#
21+
22+
package(default_visibility = ["//test/behaviour/concept/thing:__subpackages__"])
23+
24+
load("@vaticle_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test")
25+
load("//test/behaviour:rules.bzl", "typedb_behaviour_java_test")
26+
27+
java_library(
28+
name = "steps",
29+
srcs = [
30+
"SerializationSteps.java",
31+
],
32+
visibility = ["//visibility:public"],
33+
deps = [
34+
# Internal Package Dependencies
35+
"//api:api",
36+
"//test/behaviour/typeql:steps",
37+
38+
# External Maven Dependencies
39+
"@maven//:com_eclipsesource_minimal_json_minimal_json",
40+
"@maven//:io_cucumber_cucumber_java",
41+
"@maven//:junit_junit",
42+
],
43+
)
44+
45+
typedb_behaviour_java_test(
46+
name = "test",
47+
srcs = [
48+
"SerializationTest.java",
49+
],
50+
test_class = "com.vaticle.typedb.client.test.behaviour.concept.serialization.SerializationTest",
51+
data = [
52+
"@vaticle_typedb_behaviour//concept:serialization.feature",
53+
],
54+
typedb_artifact_mac = "@vaticle_typedb_artifact_mac//file",
55+
typedb_artifact_linux = "@vaticle_typedb_artifact_linux//file",
56+
typedb_artifact_windows = "@vaticle_typedb_artifact_windows//file",
57+
typedb_cluster_artifact_mac = "@vaticle_typedb_cluster_artifact_mac//file",
58+
typedb_cluster_artifact_linux = "@vaticle_typedb_cluster_artifact_linux//file",
59+
typedb_cluster_artifact_windows = "@vaticle_typedb_cluster_artifact_windows//file",
60+
connection_steps_core = "//test/behaviour/connection:steps-core",
61+
connection_steps_cluster = "//test/behaviour/connection:steps-cluster",
62+
steps = [
63+
":steps",
64+
"//test/behaviour/concept/thing/attribute:steps",
65+
"//test/behaviour/concept/thing/entity:steps",
66+
"//test/behaviour/concept/thing/relation:steps",
67+
"//test/behaviour/concept/type/attributetype:steps",
68+
"//test/behaviour/concept/type/relationtype:steps",
69+
"//test/behaviour/connection:steps-base",
70+
"//test/behaviour/connection/database:steps",
71+
"//test/behaviour/connection/session:steps",
72+
"//test/behaviour/connection/transaction:steps",
73+
],
74+
deps = [
75+
# Internal Package Dependencies
76+
"//test/behaviour:behaviour",
77+
# External Maven Dependencies
78+
"@maven//:io_cucumber_cucumber_junit",
79+
],
80+
runtime_deps = [
81+
"//test/behaviour/config:parameters",
82+
],
83+
size = "small",
84+
)
85+
86+
87+
checkstyle_test(
88+
name = "checkstyle",
89+
include = glob(["*"]),
90+
license_type = "apache-header",
91+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (C) 2022 Vaticle
3+
*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
package com.vaticle.typedb.client.test.behaviour.concept.serialization;
23+
24+
import com.eclipsesource.json.Json;
25+
import com.eclipsesource.json.JsonArray;
26+
import com.eclipsesource.json.JsonObject;
27+
import com.eclipsesource.json.JsonValue;
28+
import com.vaticle.typedb.client.api.answer.ConceptMap;
29+
import io.cucumber.java.en.Then;
30+
31+
import java.util.List;
32+
import java.util.Objects;
33+
import java.util.stream.Collectors;
34+
35+
import com.vaticle.typedb.client.test.behaviour.typeql.TypeQLSteps;
36+
37+
import static org.junit.Assert.assertEquals;
38+
import static org.junit.Assert.assertTrue;
39+
40+
public class SerializationSteps {
41+
@Then("JSON of answer concepts matches")
42+
public void json_matches(String expectedJSON) {
43+
List<JsonValue> actual = TypeQLSteps.answers().stream().map(ConceptMap::toJSON).collect(Collectors.toList());
44+
List<JsonValue> expected = Json.parse(expectedJSON).asArray().values();
45+
assertEquals(actual.size(), expected.size());
46+
for (JsonValue expectedItem: expected) {
47+
boolean foundMatch = false;
48+
for (JsonValue actualItem: actual) {
49+
if (JSONValuesAreEqual(expectedItem, actualItem)) {
50+
actual.remove(actualItem);
51+
foundMatch = true;
52+
break;
53+
}
54+
}
55+
assertTrue("No matches found for [" + expectedItem + "] in the list of answers.", foundMatch);
56+
}
57+
}
58+
59+
private boolean JSONValuesAreEqual(JsonValue left, JsonValue right) {
60+
if (Objects.equals(left, right)) return true;
61+
if (left == null || right == null) return false;
62+
if (left.isObject() && right.isObject()) return JSONObjectsAreEqual(left.asObject(), right.asObject());
63+
if (left.isArray() && right.isArray()) return JSONArraysAreEqual(left.asArray(), right.asArray());
64+
return false;
65+
}
66+
67+
private boolean JSONObjectsAreEqual(JsonObject left, JsonObject right) {
68+
if (left.size() != right.size()) return false;
69+
return left.names().stream().allMatch((name) -> JSONValuesAreEqual(left.get(name), right.get(name)));
70+
}
71+
72+
private boolean JSONArraysAreEqual(JsonArray left, JsonArray right) {
73+
if (left.size() != right.size()) return false;
74+
for (int i = 0; i < left.size(); i++)
75+
if (!JSONValuesAreEqual(left.get(i), right.get(i))) return false;
76+
return true;
77+
}
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (C) 2022 Vaticle
3+
*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
package com.vaticle.typedb.client.test.behaviour.concept.serialization;
23+
24+
import com.vaticle.typedb.client.test.behaviour.BehaviourTest;
25+
import io.cucumber.junit.Cucumber;
26+
import io.cucumber.junit.CucumberOptions;
27+
import org.junit.runner.RunWith;
28+
29+
@RunWith(Cucumber.class)
30+
@CucumberOptions(
31+
strict = true,
32+
plugin = "pretty",
33+
glue = "com.vaticle.typedb.client.test.behaviour",
34+
features = "external/vaticle_typedb_behaviour/concept/serialization.feature",
35+
tags = "not @ignore and not @ignore-typedb"
36+
)
37+
public class SerializationTest extends BehaviourTest {
38+
// ATTENTION:
39+
// When you click RUN from within this class through Intellij IDE, it will fail.
40+
// You can fix it by doing:
41+
//
42+
// 1) Go to 'Run'
43+
// 2) Select 'Edit Configurations...'
44+
// 3) Select 'Bazel test SerializationTest'
45+
//
46+
// 4) Ensure 'Target Expression' is set correctly: '//<this>/<package>/<name>:test'
47+
//
48+
// 5) Update 'Bazel Flags':
49+
// a) Remove the line that says: '--test_filter=com.vaticle.typedb.client.*'
50+
// b) Use the following Bazel flags:
51+
// --cache_test_results=no : to make sure you're not using cache
52+
// --test_output=streamed : to make sure all output is printed
53+
// --subcommands : to print the low-level commands and execution paths
54+
// --sandbox_debug : to keep the sandbox not deleted after test runs
55+
// --spawn_strategy=standalone : if you're on Mac and the tests need permission to access filesystem
56+
//
57+
// 6) Hit the RUN button by selecting the test from the dropdown menu on the top bar
58+
}

test/behaviour/typeql/TypeQLSteps.java

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ public class TypeQLSteps {
6969
private static List<ConceptMapGroup> answerGroups;
7070
private static List<NumericGroup> numericAnswerGroups;
7171
private Map<String, Map<String, String>> rules;
72+
73+
public static List<ConceptMap> answers() {
74+
return answers;
75+
}
7276

7377
@Given("the integrity is validated")
7478
public void integrity_is_validated() {

0 commit comments

Comments
 (0)