Skip to content

Commit

Permalink
Update GraphSONv4 labels, extended type names and edge properties.
Browse files Browse the repository at this point in the history
  • Loading branch information
kenhuuu committed Oct 3, 2024
1 parent 8acda80 commit edb4b8d
Show file tree
Hide file tree
Showing 41 changed files with 320 additions and 231 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONUtil.safeWriteObjectField;

/**
* GraphSON serializers for graph-based objects such as vertices, edges, properties, and paths. These serializers
* present a generalized way to serialize the implementations of core interfaces.
Expand Down Expand Up @@ -101,7 +99,7 @@ public void serialize(final Vertex vertex, final JsonGenerator jsonGenerator, fi
jsonGenerator.writeStartObject();

jsonGenerator.writeObjectField(GraphSONTokens.ID, vertex.id());
jsonGenerator.writeStringField(GraphSONTokens.LABEL, vertex.label());
writeLabel(jsonGenerator, GraphSONTokens.LABEL, vertex.label());
writeTypeForGraphObjectIfUntyped(jsonGenerator, typeInfo, GraphSONTokens.VERTEX);
writeProperties(vertex, jsonGenerator, serializerProvider);

Expand All @@ -110,8 +108,6 @@ public void serialize(final Vertex vertex, final JsonGenerator jsonGenerator, fi
}

private void writeProperties(final Vertex vertex, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) throws IOException {
if (vertex.keys().size() == 0)
return;
jsonGenerator.writeFieldName(GraphSONTokens.PROPERTIES);
jsonGenerator.writeStartObject();

Expand Down Expand Up @@ -161,10 +157,10 @@ public void serialize(final Edge edge, final JsonGenerator jsonGenerator, final
jsonGenerator.writeStartObject();

jsonGenerator.writeObjectField(GraphSONTokens.ID, edge.id());
jsonGenerator.writeStringField(GraphSONTokens.LABEL, edge.label());
writeLabel(jsonGenerator, GraphSONTokens.LABEL, edge.label());
writeTypeForGraphObjectIfUntyped(jsonGenerator, typeInfo, GraphSONTokens.EDGE);
jsonGenerator.writeStringField(GraphSONTokens.IN_LABEL, edge.inVertex().label());
jsonGenerator.writeStringField(GraphSONTokens.OUT_LABEL, edge.outVertex().label());
writeLabel(jsonGenerator, GraphSONTokens.IN_LABEL, edge.inVertex().label());
writeLabel(jsonGenerator, GraphSONTokens.OUT_LABEL, edge.outVertex().label());
jsonGenerator.writeObjectField(GraphSONTokens.IN, edge.inVertex().id());
jsonGenerator.writeObjectField(GraphSONTokens.OUT, edge.outVertex().id());
writeProperties(edge, jsonGenerator);
Expand All @@ -175,16 +171,24 @@ public void serialize(final Edge edge, final JsonGenerator jsonGenerator, final
private void writeProperties(final Edge edge, final JsonGenerator jsonGenerator) throws IOException {
final Iterator<Property<Object>> elementProperties = normalize ?
IteratorUtils.list(edge.properties(), Comparators.PROPERTY_COMPARATOR).iterator() : edge.properties();
if (elementProperties.hasNext()) {
jsonGenerator.writeFieldName(GraphSONTokens.PROPERTIES);

jsonGenerator.writeStartObject();
if (typeInfo == TypeInfo.NO_TYPES)
elementProperties.forEachRemaining(prop -> safeWriteObjectField(jsonGenerator, prop.key(), prop.value()));
else
elementProperties.forEachRemaining(prop -> safeWriteObjectField(jsonGenerator, prop.key(), prop));
jsonGenerator.writeEndObject();
jsonGenerator.writeFieldName(GraphSONTokens.PROPERTIES);
jsonGenerator.writeStartObject();

while (elementProperties.hasNext()) {
final Property prop = elementProperties.next();
jsonGenerator.writeFieldName(prop.key());
jsonGenerator.writeStartArray();

if (typeInfo == TypeInfo.NO_TYPES) {
jsonGenerator.writeObject(prop.value());
} else {
jsonGenerator.writeObject(prop);
}

jsonGenerator.writeEndArray();
}
jsonGenerator.writeEndObject();
}
}

Expand Down Expand Up @@ -229,8 +233,9 @@ private static void writeVertexProperty(final VertexProperty property, final Jso

jsonGenerator.writeObjectField(GraphSONTokens.ID, property.id());
jsonGenerator.writeObjectField(GraphSONTokens.VALUE, property.value());
if (includeLabel)
jsonGenerator.writeStringField(GraphSONTokens.LABEL, property.label());
if (includeLabel) {
writeLabel(jsonGenerator, GraphSONTokens.LABEL, property.label());
}
tryWriteMetaProperties(property, jsonGenerator, normalize);

jsonGenerator.writeEndObject();
Expand All @@ -241,14 +246,11 @@ private static void tryWriteMetaProperties(final VertexProperty property, final
// when "detached" you can't check features of the graph it detached from so it has to be
// treated differently from a regular VertexProperty implementation.
if (property instanceof DetachedVertexProperty) {
// only write meta properties key if they exist
if (property.properties().hasNext()) {
writeMetaProperties(property, jsonGenerator, normalize);
}
writeMetaProperties(property, jsonGenerator, normalize);
} else {
// still attached - so we can check the features to see if it's worth even trying to write the
// meta properties key
if (property.graph().features().vertex().supportsMetaProperties() && property.properties().hasNext()) {
if (property.graph().features().vertex().supportsMetaProperties()) {
writeMetaProperties(property, jsonGenerator, normalize);
}
}
Expand Down Expand Up @@ -460,7 +462,9 @@ public Vertex deserialize(final JsonParser jsonParser, final DeserializationCont
v.setId(deserializationContext.readValue(jsonParser, Object.class));
} else if (jsonParser.getCurrentName().equals(GraphSONTokens.LABEL)) {
jsonParser.nextToken();
v.setLabel(jsonParser.getText());
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
v.setLabel(jsonParser.getText());
}
} else if (jsonParser.getCurrentName().equals(GraphSONTokens.PROPERTIES)) {
jsonParser.nextToken();
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
Expand Down Expand Up @@ -498,24 +502,32 @@ public Edge deserialize(final JsonParser jsonParser, final DeserializationContex
e.setId(deserializationContext.readValue(jsonParser, Object.class));
} else if (jsonParser.getCurrentName().equals(GraphSONTokens.LABEL)) {
jsonParser.nextToken();
e.setLabel(jsonParser.getText());
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
e.setLabel(jsonParser.getText());
}
} else if (jsonParser.getCurrentName().equals(GraphSONTokens.OUT)) {
jsonParser.nextToken();
outV.setId(deserializationContext.readValue(jsonParser, Object.class));
} else if (jsonParser.getCurrentName().equals(GraphSONTokens.OUT_LABEL)) {
jsonParser.nextToken();
outV.setLabel(jsonParser.getText());
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
outV.setLabel(jsonParser.getText());
}
} else if (jsonParser.getCurrentName().equals(GraphSONTokens.IN)) {
jsonParser.nextToken();
inV.setId(deserializationContext.readValue(jsonParser, Object.class));
} else if (jsonParser.getCurrentName().equals(GraphSONTokens.IN_LABEL)) {
jsonParser.nextToken();
inV.setLabel(jsonParser.getText());
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
inV.setLabel(jsonParser.getText());
}
} else if (jsonParser.getCurrentName().equals(GraphSONTokens.PROPERTIES)) {
jsonParser.nextToken();
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
jsonParser.nextToken();
e.addProperty(deserializationContext.readValue(jsonParser, Property.class));
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
e.addProperty(deserializationContext.readValue(jsonParser, Property.class));
}
}
}
}
Expand Down Expand Up @@ -614,7 +626,9 @@ public VertexProperty deserialize(final JsonParser jsonParser, final Deserializa
vp.setId(deserializationContext.readValue(jsonParser, Object.class));
} else if (jsonParser.getCurrentName().equals(GraphSONTokens.LABEL)) {
jsonParser.nextToken();
vp.setLabel(jsonParser.getText());
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
vp.setLabel(jsonParser.getText());
}
} else if (jsonParser.getCurrentName().equals(GraphSONTokens.VALUE)) {
jsonParser.nextToken();
vp.setValue(deserializationContext.readValue(jsonParser, Object.class));
Expand Down Expand Up @@ -793,4 +807,15 @@ private static void writeTypeForGraphObjectIfUntyped(final JsonGenerator jsonGen
jsonGenerator.writeStringField(GraphSONTokens.TYPE, type);
}
}

/**
* Helper method for writing a label. Starting in v4, the label is an array of String that is inside an object. Only
* writes the array portion; that is, it assumes the object has already started.
*/
private static void writeLabel(final JsonGenerator jsonGenerator, final String labelName, final String labelValue) throws IOException {
jsonGenerator.writeFieldName(labelName);
jsonGenerator.writeStartArray();
jsonGenerator.writeString(labelValue);
jsonGenerator.writeEndArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public final class GraphSONXModuleV4 extends GraphSONModule {

private static final Map<Class, String> TYPE_DEFINITIONS = Collections.unmodifiableMap(
new LinkedHashMap<Class, String>() {{
put(ByteBuffer.class, "ByteBuffer");
put(ByteBuffer.class, "Binary");
put(Short.class, "Int16");
put(BigInteger.class, "BigInteger");
put(BigDecimal.class, "BigDecimal");
Expand Down Expand Up @@ -124,7 +124,7 @@ public Map<Class, String> getTypeDefinitions() {

@Override
public String getTypeNamespace() {
return GraphSONTokens.GREMLINX_TYPE_NAMESPACE;
return GraphSONTokens.GREMLIN_TYPE_NAMESPACE;
}

public static final class Builder implements GraphSONModuleBuilder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,11 @@ public void shouldReadBigIntegerAsString() throws Exception {
assumeThat(version, not(startsWith("v1")));

final BigInteger o = new BigInteger("123456789987654321123456789987654321");
assertEquals(o, mapper.readValue("{\"@type\": \"gx:BigInteger\", \"@value\": \"123456789987654321123456789987654321\"}", Object.class));
if (version.startsWith("v4")) {
assertEquals(o, mapper.readValue("{\"@type\": \"g:BigInteger\", \"@value\": \"123456789987654321123456789987654321\"}", Object.class));
} else {
assertEquals(o, mapper.readValue("{\"@type\": \"gx:BigInteger\", \"@value\": \"123456789987654321123456789987654321\"}", Object.class));
}
}

@Test
Expand All @@ -352,7 +356,11 @@ public void shouldReadBigIntegerAsNumber() throws Exception {
// enforces this approach but leaves open the opportunity to accept either. at some point in the future
// perhaps it can switch fully - TINKERPOP-2156
final BigInteger o = new BigInteger("123456789987654321123456789987654321");
assertEquals(o, mapper.readValue("{\"@type\": \"gx:BigInteger\", \"@value\": 123456789987654321123456789987654321}", Object.class));
if (version.startsWith("v4")) {
assertEquals(o, mapper.readValue("{\"@type\": \"g:BigInteger\", \"@value\": 123456789987654321123456789987654321}", Object.class));
} else {
assertEquals(o, mapper.readValue("{\"@type\": \"gx:BigInteger\", \"@value\": 123456789987654321123456789987654321}", Object.class));
}
}

@Test
Expand Down Expand Up @@ -426,7 +434,12 @@ public void shouldReadBigDecimalAsString() throws Exception {
assumeThat(version, not(startsWith("v1")));

final BigDecimal o = new BigDecimal("123456789987654321123456789987654321");
assertEquals(o, mapper.readValue("{\"@type\": \"gx:BigDecimal\", \"@value\": \"123456789987654321123456789987654321\"}", Object.class));

if (version.startsWith("v4")) {
assertEquals(o, mapper.readValue("{\"@type\": \"g:BigDecimal\", \"@value\": \"123456789987654321123456789987654321\"}", Object.class));
} else {
assertEquals(o, mapper.readValue("{\"@type\": \"gx:BigDecimal\", \"@value\": \"123456789987654321123456789987654321\"}", Object.class));
}
}

@Test
Expand All @@ -438,7 +451,11 @@ public void shouldReadBigDecimalAsNumber() throws Exception {
// enforces this approach but leaves open the opportunity to accept either. at some point in the future
// perhaps it can switch fully - TINKERPOP-2156
final BigDecimal o = new BigDecimal("123456789987654321123456789987654321");
assertEquals(o, mapper.readValue("{\"@type\": \"gx:BigDecimal\", \"@value\": 123456789987654321123456789987654321}", Object.class));
if (version.startsWith("v4")) {
assertEquals(o, mapper.readValue("{\"@type\": \"g:BigDecimal\", \"@value\": 123456789987654321123456789987654321}", Object.class));
} else {
assertEquals(o, mapper.readValue("{\"@type\": \"gx:BigDecimal\", \"@value\": 123456789987654321123456789987654321}", Object.class));
}
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ public void shouldFailIfTypeSpecifiedIsNotSameTypeInPayload() {
mapper.readValue(inputStream, Instant.class);
fail("Should have failed decoding the value");
} catch (Exception e) {
assertThat(e.getMessage(), containsString("Could not deserialize the JSON value as required. Nested exception: java.lang.InstantiationException: Cannot deserialize the value with the detected type contained in the JSON ('" + GraphSONTokens.GREMLINX_TYPE_NAMESPACE + ":ZoneOffset') to the type specified in parameter to the object mapper (class java.time.Instant). Those types are incompatible."));
if (version.startsWith("v4")) {
assertThat(e.getMessage(), containsString("Could not deserialize the JSON value as required. Nested exception: java.lang.InstantiationException: Cannot deserialize the value with the detected type contained in the JSON ('" + GraphSONTokens.GREMLIN_TYPE_NAMESPACE + ":ZoneOffset') to the type specified in parameter to the object mapper (class java.time.Instant). Those types are incompatible."));
} else {
assertThat(e.getMessage(), containsString("Could not deserialize the JSON value as required. Nested exception: java.lang.InstantiationException: Cannot deserialize the value with the detected type contained in the JSON ('" + GraphSONTokens.GREMLINX_TYPE_NAMESPACE + ":ZoneOffset') to the type specified in parameter to the object mapper (class java.time.Instant). Those types are incompatible."));
}
}
}

Expand Down
Loading

0 comments on commit edb4b8d

Please sign in to comment.