This is an automated email from the ASF dual-hosted git repository.

Cole-Greer pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit a0c04d2139787baaa0b4ce1d4b07a891e2bf8088
Merge: 71d8528449 f33bbcb13b
Author: Cole Greer <[email protected]>
AuthorDate: Thu Aug 27 13:04:21 2026 -0700

    Merge branch '3.8-dev'

 .beads/issues.jsonl                                |  11 +++
 CHANGELOG.asciidoc                                 |   1 +
 .../tinkergraph/structure/TinkerIoRegistryV2.java  |  27 +++++-
 .../tinkergraph/structure/TinkerIoRegistryV3.java  |  27 +++++-
 .../tinkergraph/structure/TinkerIoRegistryV4.java  |  27 +++++-
 .../TinkerGraphGraphSONSerializerV2Test.java       |  36 +++++++
 .../TinkerGraphGraphSONSerializerV3Test.java       | 107 +++++++++++++++++++++
 .../TinkerGraphGraphSONSerializerV4Test.java       | 107 +++++++++++++++++++++
 8 files changed, 334 insertions(+), 9 deletions(-)

diff --cc 
tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV2.java
index d151324ad7,dbe1874abb..c031880e02
--- 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV2.java
+++ 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV2.java
@@@ -187,14 -188,16 +188,16 @@@ public final class TinkerIoRegistryV2 e
          }
  
          @Override
 -        public TinkerGraph deserialize(final JsonParser jsonParser, final 
DeserializationContext deserializationContext) throws IOException, 
JsonProcessingException {
 +        public TinkerMemoryGraph deserialize(final JsonParser jsonParser, 
final DeserializationContext deserializationContext) throws IOException, 
JsonProcessingException {
              final Configuration conf = new BaseConfiguration();
              
conf.setProperty("gremlin.tinkergraph.defaultVertexPropertyCardinality", 
"list");
 -            final TinkerGraph graph = TinkerGraph.open(conf);
 +            final TinkerMemoryGraph graph = TinkerMemoryGraph.open(conf);
  
-             while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
+             while (nextTokenOrThrow(jsonParser) != JsonToken.END_OBJECT) {
                  if (jsonParser.getCurrentName().equals("vertices")) {
-                     while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
+                     if (nextTokenOrThrow(jsonParser) != JsonToken.START_ARRAY)
+                         throw new JsonParseException(jsonParser, "Expected an 
array value for the \"vertices\" field");
+                     while (nextTokenOrThrow(jsonParser) != 
JsonToken.END_ARRAY) {
                          if (jsonParser.currentToken() == 
JsonToken.START_OBJECT) {
                              final DetachedVertex v = (DetachedVertex) 
deserializationContext.readValue(jsonParser, Vertex.class);
                              v.attach(Attachable.Method.getOrCreate(graph));
diff --cc 
tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV3.java
index 435432ea6d,6252f1c867..3dacd175ed
--- 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV3.java
+++ 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV3.java
@@@ -187,14 -188,16 +188,16 @@@ public final class TinkerIoRegistryV3 e
          }
  
          @Override
 -        public TinkerGraph deserialize(final JsonParser jsonParser, final 
DeserializationContext deserializationContext) throws IOException, 
JsonProcessingException {
 +        public TinkerMemoryGraph deserialize(final JsonParser jsonParser, 
final DeserializationContext deserializationContext) throws IOException, 
JsonProcessingException {
              final Configuration conf = new BaseConfiguration();
              
conf.setProperty("gremlin.tinkergraph.defaultVertexPropertyCardinality", 
"list");
 -            final TinkerGraph graph = TinkerGraph.open(conf);
 +            final TinkerMemoryGraph graph = TinkerMemoryGraph.open(conf);
  
-             while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
+             while (nextTokenOrThrow(jsonParser) != JsonToken.END_OBJECT) {
                  if (jsonParser.getCurrentName().equals("vertices")) {
-                     while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
+                     if (nextTokenOrThrow(jsonParser) != JsonToken.START_ARRAY)
+                         throw new JsonParseException(jsonParser, "Expected an 
array value for the \"vertices\" field");
+                     while (nextTokenOrThrow(jsonParser) != 
JsonToken.END_ARRAY) {
                          if (jsonParser.currentToken() == 
JsonToken.START_OBJECT) {
                              final DetachedVertex v = (DetachedVertex) 
deserializationContext.readValue(jsonParser, Vertex.class);
                              v.attach(Attachable.Method.getOrCreate(graph));
diff --cc 
tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV4.java
index 3e39378983,0000000000..a4dbe7ea00
mode 100644,000000..100644
--- 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV4.java
+++ 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV4.java
@@@ -1,216 -1,0 +1,237 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.tinkerpop.gremlin.tinkergraph.structure;
 +
 +import org.apache.commons.configuration2.BaseConfiguration;
 +import org.apache.commons.configuration2.Configuration;
 +import org.apache.tinkerpop.gremlin.structure.Edge;
 +import org.apache.tinkerpop.gremlin.structure.Vertex;
 +import org.apache.tinkerpop.gremlin.structure.io.AbstractIoRegistry;
 +import org.apache.tinkerpop.gremlin.structure.io.GraphReader;
 +import org.apache.tinkerpop.gremlin.structure.io.GraphWriter;
 +import org.apache.tinkerpop.gremlin.structure.io.IoRegistry;
 +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONIo;
 +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONTokens;
 +import 
org.apache.tinkerpop.gremlin.structure.io.graphson.TinkerPopJacksonModule;
 +import org.apache.tinkerpop.gremlin.structure.io.gryo.GryoIo;
 +import org.apache.tinkerpop.gremlin.structure.io.gryo.GryoReader;
 +import org.apache.tinkerpop.gremlin.structure.io.gryo.GryoWriter;
 +import org.apache.tinkerpop.gremlin.structure.util.Attachable;
 +import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge;
 +import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex;
 +import org.apache.tinkerpop.shaded.jackson.core.JsonGenerator;
++import org.apache.tinkerpop.shaded.jackson.core.JsonParseException;
 +import org.apache.tinkerpop.shaded.jackson.core.JsonParser;
 +import org.apache.tinkerpop.shaded.jackson.core.JsonProcessingException;
 +import org.apache.tinkerpop.shaded.jackson.core.JsonToken;
 +import org.apache.tinkerpop.shaded.jackson.databind.DeserializationContext;
 +import org.apache.tinkerpop.shaded.jackson.databind.SerializerProvider;
 +import org.apache.tinkerpop.shaded.jackson.databind.deser.std.StdDeserializer;
 +import 
org.apache.tinkerpop.shaded.jackson.databind.ser.std.StdScalarSerializer;
 +import org.apache.tinkerpop.shaded.kryo.Kryo;
 +import org.apache.tinkerpop.shaded.kryo.Serializer;
 +import org.apache.tinkerpop.shaded.kryo.io.Input;
 +import org.apache.tinkerpop.shaded.kryo.io.Output;
 +
 +import java.io.ByteArrayInputStream;
 +import java.io.ByteArrayOutputStream;
 +import java.io.IOException;
 +import java.util.HashMap;
 +import java.util.Iterator;
 +import java.util.Map;
 +
 +/**
 + * An implementation of the {@link IoRegistry} interface that provides 
serializers with custom configurations for
 + * implementation specific classes that might need to be serialized.  This 
registry allows a {@link TinkerMemoryGraph} to
 + * be serialized directly which is useful for moving small graphs around on 
the network.
 + * <p/>
 + * Most providers need not implement this kind of custom serializer as they 
will deal with much larger graphs that
 + * wouldn't be practical to serialize in this fashion.  This is a bit of a 
special case for TinkerGraph given its
 + * in-memory status.  Typical implementations would create serializers for a 
complex vertex identifier or a
 + * custom data class like a "geographic point".
 + *
 + * @author Stephen Mallette (http://stephen.genoprime.com)
 + */
 +public final class TinkerIoRegistryV4 extends AbstractIoRegistry {
 +
 +    private static final TinkerIoRegistryV4 INSTANCE = new 
TinkerIoRegistryV4();
 +
 +    private TinkerIoRegistryV4() {
 +        register(GryoIo.class, TinkerMemoryGraph.class, new 
TinkerGraphGryoSerializer());
 +        register(GraphSONIo.class, null, new TinkerModuleV2());
 +    }
 +
 +    public static TinkerIoRegistryV4 instance() {
 +        return INSTANCE;
 +    }
 +
 +    /**
 +     * Provides a method to serialize an entire {@link TinkerMemoryGraph} 
into itself for Gryo.  This is useful when
 +     * shipping small graphs around through Gremlin Server. Reuses the 
existing Kryo instance for serialization.
 +     */
 +    final static class TinkerGraphGryoSerializer extends 
Serializer<TinkerMemoryGraph> {
 +        @Override
 +        public void write(final Kryo kryo, final Output output, final 
TinkerMemoryGraph graph) {
 +            try (final ByteArrayOutputStream stream = new 
ByteArrayOutputStream()) {
 +                GryoWriter.build().mapper(() -> 
kryo).create().writeGraph(stream, graph);
 +                final byte[] bytes = stream.toByteArray();
 +                output.writeInt(bytes.length);
 +                output.write(bytes);
 +            } catch (Exception io) {
 +                throw new RuntimeException(io);
 +            }
 +        }
 +
 +        @Override
 +        public TinkerMemoryGraph read(final Kryo kryo, final Input input, 
final Class<TinkerMemoryGraph> tinkerGraphClass) {
 +            final Configuration conf = new BaseConfiguration();
 +            
conf.setProperty("gremlin.tinkergraph.defaultVertexPropertyCardinality", 
"list");
 +            final TinkerMemoryGraph graph = TinkerMemoryGraph.open(conf);
 +            final int len = input.readInt();
 +            final byte[] bytes = input.readBytes(len);
 +            try (final ByteArrayInputStream stream = new 
ByteArrayInputStream(bytes)) {
 +                GryoReader.build().mapper(() -> 
kryo).create().readGraph(stream, graph);
 +            } catch (Exception io) {
 +                throw new RuntimeException(io);
 +            }
 +
 +            return graph;
 +        }
 +    }
 +
 +    /**
 +     * Provides a method to serialize an entire {@link TinkerMemoryGraph} 
into itself for GraphSON. This is useful when
 +     * shipping small graphs around through Gremlin Server.
 +     */
 +    final static class TinkerModuleV2 extends TinkerPopJacksonModule {
 +        public TinkerModuleV2() {
 +            super("tinkergraph-2.0");
 +            addSerializer(TinkerMemoryGraph.class, new 
TinkerGraphJacksonSerializer());
 +            addDeserializer(TinkerMemoryGraph.class, new 
TinkerGraphJacksonDeserializer());
 +        }
 +
 +        @Override
 +        public Map<Class, String> getTypeDefinitions() {
 +            return new HashMap<Class, String>(){{
 +                put(TinkerMemoryGraph.class, "graph");
 +            }};
 +        }
 +
 +        @Override
 +        public String getTypeNamespace() {
 +            return GraphSONTokens.GREMLIN_TYPE_NAMESPACE;
 +        }
 +    }
 +
 +    /**
 +     * Serializes the graph into an edge list format.  Edge list is a better 
choices than adjacency list (which is
 +     * typically standard from the {@link GraphReader} and {@link 
GraphWriter} perspective) in this case because
 +     * the use case for this isn't around massive graphs.  The use case is 
for "small" subgraphs that are being
 +     * shipped over the wire from Gremlin Server. Edge list format is a bit 
easier for non-JVM languages to work
 +     * with as a format and doesn't require a cache for loading (as vertex 
labels are not serialized in adjacency
 +     * list).
 +     */
 +    final static class TinkerGraphJacksonSerializer extends 
StdScalarSerializer<TinkerMemoryGraph> {
 +
 +        public TinkerGraphJacksonSerializer() {
 +            super(TinkerMemoryGraph.class);
 +        }
 +
 +        @Override
 +        public void serialize(final TinkerMemoryGraph graph, final 
JsonGenerator jsonGenerator, final SerializerProvider serializerProvider)
 +                throws IOException {
 +            jsonGenerator.writeStartObject();
 +            jsonGenerator.writeFieldName(GraphSONTokens.VERTICES);
 +            jsonGenerator.writeStartArray();
 +
 +            final Iterator<Vertex> vertices = graph.vertices();
 +            while (vertices.hasNext()) {
 +                serializerProvider.defaultSerializeValue(vertices.next(), 
jsonGenerator);
 +            }
 +
 +            jsonGenerator.writeEndArray();
 +            jsonGenerator.writeFieldName(GraphSONTokens.EDGES);
 +            jsonGenerator.writeStartArray();
 +
 +            final Iterator<Edge> edges = graph.edges();
 +            while (edges.hasNext()) {
 +                serializerProvider.defaultSerializeValue(edges.next(), 
jsonGenerator);
 +            }
 +
 +            jsonGenerator.writeEndArray();
 +            jsonGenerator.writeEndObject();
 +        }
 +    }
 +
 +    /**
 +     * Deserializes the edge list format.
 +     */
 +    static class TinkerGraphJacksonDeserializer extends 
StdDeserializer<TinkerMemoryGraph> {
 +        public TinkerGraphJacksonDeserializer() {
 +            super(TinkerMemoryGraph.class);
 +        }
 +
 +        @Override
 +        public TinkerMemoryGraph deserialize(final JsonParser jsonParser, 
final DeserializationContext deserializationContext) throws IOException, 
JsonProcessingException {
 +            final Configuration conf = new BaseConfiguration();
 +            
conf.setProperty("gremlin.tinkergraph.defaultVertexPropertyCardinality", 
"list");
 +            final TinkerMemoryGraph graph = TinkerMemoryGraph.open(conf);
 +
-             while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
++            while (nextTokenOrThrow(jsonParser) != JsonToken.END_OBJECT) {
 +                if (jsonParser.getCurrentName().equals("vertices")) {
-                     while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
++                    if (nextTokenOrThrow(jsonParser) != JsonToken.START_ARRAY)
++                        throw new JsonParseException(jsonParser, "Expected an 
array value for the \"vertices\" field");
++                    while (nextTokenOrThrow(jsonParser) != 
JsonToken.END_ARRAY) {
 +                        if (jsonParser.currentToken() == 
JsonToken.START_OBJECT) {
 +                            final DetachedVertex v = (DetachedVertex) 
deserializationContext.readValue(jsonParser, Vertex.class);
 +                            v.attach(Attachable.Method.getOrCreate(graph));
 +                        }
 +                    }
 +                } else if (jsonParser.getCurrentName().equals("edges")) {
-                     while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
++                    if (nextTokenOrThrow(jsonParser) != JsonToken.START_ARRAY)
++                        throw new JsonParseException(jsonParser, "Expected an 
array value for the \"edges\" field");
++                    while (nextTokenOrThrow(jsonParser) != 
JsonToken.END_ARRAY) {
 +                        if (jsonParser.currentToken() == 
JsonToken.START_OBJECT) {
 +                            final DetachedEdge e = (DetachedEdge) 
deserializationContext.readValue(jsonParser, Edge.class);
 +                            e.attach(Attachable.Method.getOrCreate(graph));
 +                        }
 +                    }
 +                }
 +            }
 +
 +            return graph;
 +        }
++
++        /**
++         * Advances the parser one token, treating end-of-input as a parse 
error.  Once the
++         * underlying input is exhausted, {@code JsonParser.nextToken()} 
returns {@code null} on
++         * every subsequent call rather than throwing.  A loop whose only 
exit condition compares
++         * the result against a structural close token (e.g. {@code 
END_ARRAY}) would therefore
++         * never terminate on truncated or malformed input.  This wrapper 
converts the {@code null}
++         * return into a {@code JsonParseException} so that callers can use a 
simple while-loop
++         * idiom without risk of non-termination.
++         */
++        private static JsonToken nextTokenOrThrow(final JsonParser 
jsonParser) throws IOException {
++            final JsonToken token = jsonParser.nextToken();
++            if (null == token)
++                throw new JsonParseException(jsonParser, "Unexpected 
end-of-input while reading a TinkerGraph");
++            return token;
++        }
 +    }
 +}
diff --cc 
tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV2Test.java
index 67c986f498,b795764f20..32eb7d38a6
--- 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV2Test.java
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV2Test.java
@@@ -338,6 -338,42 +338,42 @@@ public class TinkerGraphGraphSONSeriali
          }
      }
  
+     @Test(timeout = 5000)
+     public void shouldFailFastOnScalarVerticesField() throws IOException {
+         final String malformed = 
"{\"@type\":\"tinker:graph\",\"@value\":{\"vertices\":0}}";
+         final GraphReader reader = getReader(defaultMapperV2);
+         try {
 -            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerGraph.class);
++            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerMemoryGraph.class);
+             fail("Expected IOException for malformed tinker:graph input");
+         } catch (IOException expected) {
+             // JsonParseException — the START_ARRAY check threw as intended
+         }
+     }
+ 
+     @Test(timeout = 5000)
+     public void shouldFailFastOnScalarEdgesField() throws IOException {
+         final String malformed = 
"{\"@type\":\"tinker:graph\",\"@value\":{\"vertices\":[],\"edges\":0}}";
+         final GraphReader reader = getReader(defaultMapperV2);
+         try {
 -            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerGraph.class);
++            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerMemoryGraph.class);
+             fail("Expected IOException for malformed tinker:graph input");
+         } catch (IOException expected) {
+             // JsonParseException — the START_ARRAY check threw as intended
+         }
+     }
+ 
+     @Test(timeout = 5000)
+     public void shouldFailFastOnTruncatedInput() throws IOException {
+         final String malformed = 
"{\"@type\":\"tinker:graph\",\"@value\":{\"vertices\":[";
+         final GraphReader reader = getReader(defaultMapperV2);
+         try {
 -            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerGraph.class);
++            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerMemoryGraph.class);
+             fail("Expected IOException for truncated tinker:graph input");
+         } catch (IOException expected) {
+             // JsonParseException — nextTokenOrThrow detected end-of-input
+         }
+     }
+ 
      @Test
      public void deserializersTestsProperty() {
          final TinkerGraph tg = TinkerGraph.open();
diff --cc 
tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV3Test.java
index 0000000000,727985ee8f..f00fa89dcf
mode 000000,100644..100644
--- 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV3Test.java
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV3Test.java
@@@ -1,0 -1,107 +1,107 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *   http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.tinkerpop.gremlin.tinkergraph.structure;
+ 
+ import org.apache.tinkerpop.gremlin.structure.io.GraphReader;
+ import org.apache.tinkerpop.gremlin.structure.io.GraphWriter;
+ import org.apache.tinkerpop.gremlin.structure.io.Mapper;
+ import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper;
+ import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONReader;
+ import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion;
+ import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONWriter;
+ import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONXModuleV3;
+ import org.junit.Test;
+ 
+ import java.io.ByteArrayInputStream;
+ import java.io.ByteArrayOutputStream;
+ import java.io.IOException;
+ 
+ import static org.junit.Assert.assertEquals;
+ import static org.junit.Assert.fail;
+ 
+ /**
+  * Serialization and deserialization tests for GraphSON V3 with the 
TinkerGraph custom type.
+  */
+ public class TinkerGraphGraphSONSerializerV3Test {
+ 
+     private final Mapper defaultMapperV3 = GraphSONMapper.build()
+             .version(GraphSONVersion.V3_0)
+             .addCustomModule(GraphSONXModuleV3.build())
+             .addRegistry(TinkerIoRegistryV3.instance())
+             .create();
+ 
+     @Test
+     public void shouldDeserializeWellFormedGraph() throws IOException {
 -        final TinkerGraph original = TinkerFactory.createModern();
++        final TinkerMemoryGraph original = TinkerFactory.createModern();
+         final GraphWriter writer = getWriter(defaultMapperV3);
+         final GraphReader reader = getReader(defaultMapperV3);
+ 
+         try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+             writer.writeObject(out, original);
+             final String json = out.toString();
 -            final TinkerGraph read = reader.readObject(new 
ByteArrayInputStream(json.getBytes()), TinkerGraph.class);
++            final TinkerMemoryGraph read = reader.readObject(new 
ByteArrayInputStream(json.getBytes()), TinkerMemoryGraph.class);
+             assertEquals(6L, read.traversal().V().count().next().longValue());
+             assertEquals(6L, read.traversal().E().count().next().longValue());
+         }
+     }
+ 
+     @Test(timeout = 5000)
+     public void shouldFailFastOnScalarVerticesField() throws IOException {
+         final String malformed = 
"{\"@type\":\"tinker:graph\",\"@value\":{\"vertices\":0}}";
+         final GraphReader reader = getReader(defaultMapperV3);
+         try {
 -            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerGraph.class);
++            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerMemoryGraph.class);
+             fail("Expected IOException for malformed tinker:graph input");
+         } catch (IOException expected) {
+             // JsonParseException — the START_ARRAY check threw as intended
+         }
+     }
+ 
+     @Test(timeout = 5000)
+     public void shouldFailFastOnScalarEdgesField() throws IOException {
+         final String malformed = 
"{\"@type\":\"tinker:graph\",\"@value\":{\"vertices\":[],\"edges\":0}}";
+         final GraphReader reader = getReader(defaultMapperV3);
+         try {
 -            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerGraph.class);
++            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerMemoryGraph.class);
+             fail("Expected IOException for malformed tinker:graph input");
+         } catch (IOException expected) {
+             // JsonParseException — the START_ARRAY check threw as intended
+         }
+     }
+ 
+     @Test(timeout = 5000)
+     public void shouldFailFastOnTruncatedInput() throws IOException {
+         final String malformed = 
"{\"@type\":\"tinker:graph\",\"@value\":{\"vertices\":[";
+         final GraphReader reader = getReader(defaultMapperV3);
+         try {
 -            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerGraph.class);
++            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerMemoryGraph.class);
+             fail("Expected IOException for truncated tinker:graph input");
+         } catch (IOException expected) {
+             // JsonParseException — nextTokenOrThrow detected end-of-input
+         }
+     }
+ 
+     private GraphWriter getWriter(Mapper paramMapper) {
+         return GraphSONWriter.build().mapper(paramMapper).create();
+     }
+ 
+     private GraphReader getReader(Mapper paramMapper) {
+         return GraphSONReader.build().mapper(paramMapper).create();
+     }
+ }
diff --cc 
tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV4Test.java
index 0000000000,0000000000..7b2c012879
new file mode 100644
--- /dev/null
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV4Test.java
@@@ -1,0 -1,0 +1,107 @@@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *   http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.tinkerpop.gremlin.tinkergraph.structure;
++
++import org.apache.tinkerpop.gremlin.structure.io.GraphReader;
++import org.apache.tinkerpop.gremlin.structure.io.GraphWriter;
++import org.apache.tinkerpop.gremlin.structure.io.Mapper;
++import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper;
++import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONReader;
++import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion;
++import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONWriter;
++import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONXModuleV4;
++import org.junit.Test;
++
++import java.io.ByteArrayInputStream;
++import java.io.ByteArrayOutputStream;
++import java.io.IOException;
++
++import static org.junit.Assert.assertEquals;
++import static org.junit.Assert.fail;
++
++/**
++ * Serialization and deserialization tests for GraphSON V4 with the 
TinkerGraph custom type.
++ */
++public class TinkerGraphGraphSONSerializerV4Test {
++
++    private final Mapper defaultMapperV4 = GraphSONMapper.build()
++            .version(GraphSONVersion.V4_0)
++            .addCustomModule(GraphSONXModuleV4.build())
++            .addRegistry(TinkerIoRegistryV4.instance())
++            .create();
++
++    @Test
++    public void shouldDeserializeWellFormedGraph() throws IOException {
++        final TinkerMemoryGraph original = TinkerFactory.createModern();
++        final GraphWriter writer = getWriter(defaultMapperV4);
++        final GraphReader reader = getReader(defaultMapperV4);
++
++        try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
++            writer.writeObject(out, original);
++            final String json = out.toString();
++            final TinkerMemoryGraph read = reader.readObject(new 
ByteArrayInputStream(json.getBytes()), TinkerMemoryGraph.class);
++            assertEquals(6L, read.traversal().V().count().next().longValue());
++            assertEquals(6L, read.traversal().E().count().next().longValue());
++        }
++    }
++
++    @Test(timeout = 5000)
++    public void shouldFailFastOnScalarVerticesField() throws IOException {
++        final String malformed = 
"{\"@type\":\"g:graph\",\"@value\":{\"vertices\":0}}";
++        final GraphReader reader = getReader(defaultMapperV4);
++        try {
++            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerMemoryGraph.class);
++            fail("Expected IOException for malformed g:graph input");
++        } catch (IOException expected) {
++            // JsonParseException — the START_ARRAY check threw as intended
++        }
++    }
++
++    @Test(timeout = 5000)
++    public void shouldFailFastOnScalarEdgesField() throws IOException {
++        final String malformed = 
"{\"@type\":\"g:graph\",\"@value\":{\"vertices\":[],\"edges\":0}}";
++        final GraphReader reader = getReader(defaultMapperV4);
++        try {
++            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerMemoryGraph.class);
++            fail("Expected IOException for malformed g:graph input");
++        } catch (IOException expected) {
++            // JsonParseException — the START_ARRAY check threw as intended
++        }
++    }
++
++    @Test(timeout = 5000)
++    public void shouldFailFastOnTruncatedInput() throws IOException {
++        final String malformed = 
"{\"@type\":\"g:graph\",\"@value\":{\"vertices\":[";
++        final GraphReader reader = getReader(defaultMapperV4);
++        try {
++            reader.readObject(new ByteArrayInputStream(malformed.getBytes()), 
TinkerMemoryGraph.class);
++            fail("Expected IOException for truncated g:graph input");
++        } catch (IOException expected) {
++            // JsonParseException — nextTokenOrThrow detected end-of-input
++        }
++    }
++
++    private GraphWriter getWriter(Mapper paramMapper) {
++        return GraphSONWriter.build().mapper(paramMapper).create();
++    }
++
++    private GraphReader getReader(Mapper paramMapper) {
++        return GraphSONReader.build().mapper(paramMapper).create();
++    }
++}

Reply via email to