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

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

commit 38d3850ff755bfacc709beee1c90af255361ddde
Author: Cole Greer <[email protected]>
AuthorDate: Wed Jun 24 13:35:07 2026 -0700

    Add GraphSON V4 serialization for PrimitivePDT (g:PrimitivePdt)
    
    Adds GraphSON V4 support for PrimitiveProviderDefinedType under the
    g:PrimitivePdt type tag.
    
    - PrimitiveProviderDefinedTypeJacksonSerializer emits
      {"type": <name>, "value": <value>} with value as an untyped JSON string
      (per the GraphSON spec; the value is the opaque stringified primitive).
    - PrimitiveProviderDefinedTypeJacksonDeserializer parses type/value and
      hydrates via the ProviderDefinedTypeRegistry when set.
    - GraphSONModule (V4) maps PrimitiveProviderDefinedType -> "PrimitivePdt",
      registers the ser/deser, and threads the registry to the primitive
      deserializer via setPdtRegistry.
    - Write-side adapter fallback (PdtGraphSONSerializerProviderV4 /
      GraphSONTypeIdResolver) extended so a raw object with a registered
      PrimitivePDTAdapter serializes as g:PrimitivePdt.
    
    Response-only in T4; both directions implemented for round-trip tests.
    
    Tests: PdtGraphSONSerializersV4Test extended with g:PrimitivePdt
    serialize/deserialize, registry hydration, and 
primitive-nested-in-composite.
    
    tinkerpop-2gy.4
    
    Assisted-by: Kiro:claude-opus-4.8
---
 .../structure/io/graphson/GraphSONModule.java      |   7 ++
 .../io/graphson/GraphSONTypeIdResolver.java        |   3 +
 .../graphson/PdtGraphSONSerializerProviderV4.java  |  11 ++-
 .../io/graphson/PdtGraphSONSerializersV4.java      | 106 +++++++++++++++++++++
 .../io/graphson/PdtGraphSONSerializersV4Test.java  | 101 ++++++++++++++++++++
 5 files changed, 226 insertions(+), 2 deletions(-)

diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
index 5cdb5cb1c3..40ca8ca59f 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
@@ -82,6 +82,7 @@ import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
 import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedType;
+import 
org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitiveProviderDefinedType;
 import 
org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeRegistry;
 import org.apache.tinkerpop.gremlin.structure.util.star.DirectionalStarGraph;
 import 
org.apache.tinkerpop.gremlin.structure.util.star.StarGraphGraphSONSerializerV1;
@@ -159,6 +160,7 @@ abstract class GraphSONModule extends 
TinkerPopJacksonModule {
                     put(VertexProperty.class, "VertexProperty");
                     put(Tree.class, "Tree");
                     put(ProviderDefinedType.class, "CompositePdt");
+                    put(PrimitiveProviderDefinedType.class, "PrimitivePdt");
                     Stream.of(
                             Direction.class,
                             Merge.class,
@@ -166,6 +168,7 @@ abstract class GraphSONModule extends 
TinkerPopJacksonModule {
                 }});
 
         private final 
PdtGraphSONSerializersV4.ProviderDefinedTypeJacksonDeserializer pdtDeserializer;
+        private final 
PdtGraphSONSerializersV4.PrimitiveProviderDefinedTypeJacksonDeserializer 
primitivePdtDeserializer;
 
         /**
          * Constructs a new object.
@@ -184,6 +187,7 @@ abstract class GraphSONModule extends 
TinkerPopJacksonModule {
             addSerializer(DirectionalStarGraph.class, new 
StarGraphGraphSONSerializerV4(normalize));
             addSerializer(Tree.class, new 
GraphSONSerializersV4.TreeJacksonSerializer());
             addSerializer(ProviderDefinedType.class, new 
PdtGraphSONSerializersV4.ProviderDefinedTypeJacksonSerializer());
+            addSerializer(PrimitiveProviderDefinedType.class, new 
PdtGraphSONSerializersV4.PrimitiveProviderDefinedTypeJacksonSerializer());
 
             // java.util - use the standard jackson serializers for 
collections when types aren't embedded
             if (typeInfo != TypeInfo.NO_TYPES) {
@@ -216,6 +220,8 @@ abstract class GraphSONModule extends 
TinkerPopJacksonModule {
             addDeserializer(Tree.class, new 
GraphSONSerializersV4.TreeJacksonDeserializer());
             pdtDeserializer = new 
PdtGraphSONSerializersV4.ProviderDefinedTypeJacksonDeserializer();
             addDeserializer(ProviderDefinedType.class, pdtDeserializer);
+            primitivePdtDeserializer = new 
PdtGraphSONSerializersV4.PrimitiveProviderDefinedTypeJacksonDeserializer();
+            addDeserializer(PrimitiveProviderDefinedType.class, 
primitivePdtDeserializer);
 
             // java.util - use the standard jackson serializers for 
collections when types aren't embedded
             if (typeInfo != TypeInfo.NO_TYPES) {
@@ -242,6 +248,7 @@ abstract class GraphSONModule extends 
TinkerPopJacksonModule {
 
         void setPdtRegistry(final ProviderDefinedTypeRegistry registry) {
             pdtDeserializer.setRegistry(registry);
+            primitivePdtDeserializer.setRegistry(registry);
         }
 
         @Override
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeIdResolver.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeIdResolver.java
index 0b535f8141..cec70fb058 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeIdResolver.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONTypeIdResolver.java
@@ -88,6 +88,9 @@ public class GraphSONTypeIdResolver implements TypeIdResolver 
{
             if (pdtRegistry != null && 
pdtRegistry.getAdapterByClass(aClass).isPresent()) {
                 return 
typeToId.get(org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedType.class);
             }
+            if (pdtRegistry != null && 
pdtRegistry.getPrimitiveAdapterByClass(aClass).isPresent()) {
+                return 
typeToId.get(org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitiveProviderDefinedType.class);
+            }
             // If one wants to serialize an object with a type, but hasn't 
registered
             // a typeID for that class, fail.
             throw new IllegalArgumentException(String.format("Could not find a 
type identifier for the class : %s. " +
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializerProviderV4.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializerProviderV4.java
index fb29453da5..008be0ec3b 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializerProviderV4.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializerProviderV4.java
@@ -33,20 +33,24 @@ final class PdtGraphSONSerializerProviderV4 extends 
DefaultSerializerProvider {
     private static final long serialVersionUID = 1L;
     private final ProviderDefinedTypeRegistry pdtRegistry;
     private final JsonSerializer<Object> pdtAdapterSerializer;
+    private final JsonSerializer<Object> primitivePdtAdapterSerializer;
 
     PdtGraphSONSerializerProviderV4(final ProviderDefinedTypeRegistry 
pdtRegistry) {
         super();
         this.pdtRegistry = pdtRegistry;
         this.pdtAdapterSerializer = new 
PdtGraphSONSerializersV4.PdtAdapterJacksonSerializer(pdtRegistry);
+        this.primitivePdtAdapterSerializer = new 
PdtGraphSONSerializersV4.PrimitivePdtAdapterJacksonSerializer(pdtRegistry);
     }
 
     private PdtGraphSONSerializerProviderV4(final SerializerProvider src,
                                             final SerializationConfig config, 
final SerializerFactory f,
                                             final ProviderDefinedTypeRegistry 
pdtRegistry,
-                                            final JsonSerializer<Object> 
pdtAdapterSerializer) {
+                                            final JsonSerializer<Object> 
pdtAdapterSerializer,
+                                            final JsonSerializer<Object> 
primitivePdtAdapterSerializer) {
         super(src, config, f);
         this.pdtRegistry = pdtRegistry;
         this.pdtAdapterSerializer = pdtAdapterSerializer;
+        this.primitivePdtAdapterSerializer = primitivePdtAdapterSerializer;
     }
 
     @Override
@@ -54,12 +58,15 @@ final class PdtGraphSONSerializerProviderV4 extends 
DefaultSerializerProvider {
         if (pdtRegistry != null && 
pdtRegistry.getAdapterByClass(aClass).isPresent()) {
             return pdtAdapterSerializer;
         }
+        if (pdtRegistry != null && 
pdtRegistry.getPrimitiveAdapterByClass(aClass).isPresent()) {
+            return primitivePdtAdapterSerializer;
+        }
         return super.getUnknownTypeSerializer(aClass);
     }
 
     @Override
     public PdtGraphSONSerializerProviderV4 createInstance(final 
SerializationConfig config,
                                                           final 
SerializerFactory jsf) {
-        return new PdtGraphSONSerializerProviderV4(this, config, jsf, 
pdtRegistry, pdtAdapterSerializer);
+        return new PdtGraphSONSerializerProviderV4(this, config, jsf, 
pdtRegistry, pdtAdapterSerializer, primitivePdtAdapterSerializer);
     }
 }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4.java
index 5c0c72e1bf..193392f8b1 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4.java
@@ -19,6 +19,8 @@
 package org.apache.tinkerpop.gremlin.structure.io.graphson;
 
 import org.apache.tinkerpop.gremlin.structure.io.pdt.CompositePDTAdapter;
+import org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitivePDTAdapter;
+import 
org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitiveProviderDefinedType;
 import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedType;
 import 
org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter;
 import 
org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeRegistry;
@@ -118,6 +120,66 @@ final class PdtGraphSONSerializersV4 {
         }
     }
 
+    final static class PrimitiveProviderDefinedTypeJacksonSerializer extends 
StdScalarSerializer<PrimitiveProviderDefinedType> {
+
+        public PrimitiveProviderDefinedTypeJacksonSerializer() {
+            super(PrimitiveProviderDefinedType.class);
+        }
+
+        @Override
+        public void serialize(final PrimitiveProviderDefinedType pdt, final 
JsonGenerator jsonGenerator,
+                              final SerializerProvider serializerProvider) 
throws IOException {
+            jsonGenerator.writeStartObject();
+            jsonGenerator.writeStringField("type", pdt.getName());
+            jsonGenerator.writeStringField("value", pdt.getValue());
+            jsonGenerator.writeEndObject();
+        }
+    }
+
+    static class PrimitiveProviderDefinedTypeJacksonDeserializer extends 
StdDeserializer<PrimitiveProviderDefinedType> {
+
+        private ProviderDefinedTypeRegistry registry;
+
+        public PrimitiveProviderDefinedTypeJacksonDeserializer() {
+            super(PrimitiveProviderDefinedType.class);
+        }
+
+        void setRegistry(final ProviderDefinedTypeRegistry registry) {
+            this.registry = registry;
+        }
+
+        @Override
+        public PrimitiveProviderDefinedType deserialize(final JsonParser 
jsonParser, final DeserializationContext deserializationContext)
+                throws IOException {
+            String typeName = null;
+            String value = null;
+
+            while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
+                final String fieldName = jsonParser.getCurrentName();
+                jsonParser.nextToken();
+                if ("type".equals(fieldName)) {
+                    typeName = jsonParser.getText();
+                } else if ("value".equals(fieldName)) {
+                    value = jsonParser.getText();
+                }
+            }
+
+            final PrimitiveProviderDefinedType pdt = new 
PrimitiveProviderDefinedType(typeName, value);
+            if (registry != null) {
+                final Object hydrated = registry.hydratePrimitive(pdt);
+                if (hydrated instanceof PrimitiveProviderDefinedType)
+                    return (PrimitiveProviderDefinedType) hydrated;
+                return pdt.withHydrated(hydrated);
+            }
+            return pdt;
+        }
+
+        @Override
+        public boolean isCachable() {
+            return true;
+        }
+    }
+
     /**
      * A serializer that converts raw objects to {@link ProviderDefinedType} 
using a registered adapter,
      * then serializes the resulting PDT in the standard CompositePdt format.
@@ -168,4 +230,48 @@ final class PdtGraphSONSerializersV4 {
             return new ProviderDefinedType(adapter.typeName(), fields);
         }
     }
+
+    /**
+     * A serializer that converts raw objects to {@link 
PrimitiveProviderDefinedType} using a registered
+     * {@link PrimitivePDTAdapter}, then serializes the result in the standard 
PrimitivePdt format.
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    static class PrimitivePdtAdapterJacksonSerializer extends 
StdSerializer<Object> {
+
+        private final ProviderDefinedTypeRegistry registry;
+
+        PrimitivePdtAdapterJacksonSerializer(final ProviderDefinedTypeRegistry 
registry) {
+            super(Object.class);
+            this.registry = registry;
+        }
+
+        @Override
+        public void serialize(final Object value, final JsonGenerator 
jsonGenerator,
+                              final SerializerProvider serializerProvider) 
throws IOException {
+            final PrimitiveProviderDefinedType pdt = toPrimitivePdt(value);
+            jsonGenerator.writeStartObject();
+            jsonGenerator.writeStringField("type", pdt.getName());
+            jsonGenerator.writeStringField("value", pdt.getValue());
+            jsonGenerator.writeEndObject();
+        }
+
+        @Override
+        public void serializeWithType(final Object value, final JsonGenerator 
jsonGenerator,
+                                      final SerializerProvider 
serializerProvider,
+                                      final 
org.apache.tinkerpop.shaded.jackson.databind.jsontype.TypeSerializer 
typeSerializer) throws IOException {
+            final PrimitiveProviderDefinedType pdt = toPrimitivePdt(value);
+            
serializerProvider.findTypedValueSerializer(PrimitiveProviderDefinedType.class, 
true, null)
+                    .serialize(pdt, jsonGenerator, serializerProvider);
+        }
+
+        private PrimitiveProviderDefinedType toPrimitivePdt(final Object 
value) throws IOException {
+            final Optional<PrimitivePDTAdapter<?>> opt = 
registry.getPrimitiveAdapterByClass(value.getClass());
+            if (!opt.isPresent()) {
+                throw new IOException("No primitive adapter found for " + 
value.getClass().getName());
+            }
+            final PrimitivePDTAdapter adapter = opt.get();
+            final String strValue = adapter.toValue(value);
+            return new PrimitiveProviderDefinedType(adapter.typeName(), 
strValue);
+        }
+    }
 }
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4Test.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4Test.java
index 913d06bc51..f0fc71aff5 100644
--- 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4Test.java
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/PdtGraphSONSerializersV4Test.java
@@ -19,6 +19,8 @@
 package org.apache.tinkerpop.gremlin.structure.io.graphson;
 
 import org.apache.tinkerpop.gremlin.structure.io.pdt.CompositePDTAdapter;
+import org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitivePDTAdapter;
+import 
org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitiveProviderDefinedType;
 import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedType;
 import 
org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeRegistry;
 import org.apache.tinkerpop.shaded.jackson.databind.JsonNode;
@@ -248,4 +250,103 @@ public class PdtGraphSONSerializersV4Test extends 
AbstractGraphSONTest {
         assertEquals("Unknown", result.getName());
         assertEquals(1, result.getFields().get("a"));
     }
+
+    // --- PrimitivePDT tests ---
+
+    @Test
+    public void shouldSerializePrimitivePdt() throws Exception {
+        final PrimitiveProviderDefinedType pdt = new 
PrimitiveProviderDefinedType("Duration", "PT5M");
+
+        final String json = mapper.writeValueAsString(pdt);
+        final JsonNode node = plainMapper.readTree(json);
+
+        assertEquals("g:PrimitivePdt", node.get("@type").asText());
+        final JsonNode value = node.get("@value");
+        assertEquals("Duration", value.get("type").asText());
+        // value must be an untyped JSON string (no @type/@value wrapping)
+        assertTrue(value.get("value").isTextual());
+        assertEquals("PT5M", value.get("value").asText());
+    }
+
+    @Test
+    public void shouldDeserializePrimitivePdt() throws Exception {
+        final String json = 
"{\"@type\":\"g:PrimitivePdt\",\"@value\":{\"type\":\"Duration\",\"value\":\"PT5M\"}}";
+        final PrimitiveProviderDefinedType pdt = mapper.readValue(json, 
PrimitiveProviderDefinedType.class);
+
+        assertEquals("Duration", pdt.getName());
+        assertEquals("PT5M", pdt.getValue());
+    }
+
+    @Test
+    public void shouldRoundTripPrimitivePdt() throws Exception {
+        final PrimitiveProviderDefinedType original = new 
PrimitiveProviderDefinedType("Duration", "PT5M");
+        final PrimitiveProviderDefinedType result = 
serializeDeserialize(mapper, original, PrimitiveProviderDefinedType.class);
+
+        assertEquals(original.getName(), result.getName());
+        assertEquals(original.getValue(), result.getValue());
+    }
+
+    @Test
+    public void shouldHydratePrimitivePdtWhenRegistryConfigured() throws 
Exception {
+        final ProviderDefinedTypeRegistry registry = 
ProviderDefinedTypeRegistry.empty();
+        registry.register(new DurationAdapter());
+
+        final ObjectMapper hydratingMapper = GraphSONMapper.build()
+                .version(GraphSONVersion.V4_0)
+                .addCustomModule(GraphSONXModuleV4.build())
+                .typeInfo(TypeInfo.PARTIAL_TYPES)
+                .pdtRegistry(registry)
+                .create().createMapper();
+
+        final PrimitiveProviderDefinedType pdt = new 
PrimitiveProviderDefinedType("Duration", "PT10S");
+        final PrimitiveProviderDefinedType result = 
serializeDeserialize(hydratingMapper, pdt, PrimitiveProviderDefinedType.class);
+
+        assertNotNull(result.getHydrated());
+        assertTrue(result.getHydrated() instanceof MyDuration);
+        assertEquals(10, ((MyDuration) result.getHydrated()).seconds);
+    }
+
+    @Test
+    public void shouldNestPrimitivePdtInsideCompositePdt() throws Exception {
+        final PrimitiveProviderDefinedType inner = new 
PrimitiveProviderDefinedType("Duration", "PT1H");
+        final Map<String, Object> outerFields = new LinkedHashMap<>();
+        outerFields.put("name", "timeout");
+        outerFields.put("dur", inner);
+        final ProviderDefinedType outer = new ProviderDefinedType("Config", 
outerFields);
+
+        final String json = mapper.writeValueAsString(outer);
+        final JsonNode node = plainMapper.readTree(json);
+
+        assertEquals("g:CompositePdt", node.get("@type").asText());
+        final JsonNode durNode = node.get("@value").get("fields").get("dur");
+        assertEquals("g:PrimitivePdt", durNode.get("@type").asText());
+        assertEquals("Duration", durNode.get("@value").get("type").asText());
+        assertEquals("PT1H", durNode.get("@value").get("value").asText());
+
+        // round-trip
+        final ProviderDefinedType result = serializeDeserialize(mapper, outer, 
ProviderDefinedType.class);
+        assertEquals("Config", result.getName());
+        assertTrue(result.getFields().get("dur") instanceof 
PrimitiveProviderDefinedType);
+        final PrimitiveProviderDefinedType nestedResult = 
(PrimitiveProviderDefinedType) result.getFields().get("dur");
+        assertEquals("Duration", nestedResult.getName());
+        assertEquals("PT1H", nestedResult.getValue());
+    }
+
+    // helper types for primitive PDT hydration tests
+
+    static class MyDuration {
+        final int seconds;
+        MyDuration(int seconds) { this.seconds = seconds; }
+    }
+
+    static class DurationAdapter implements PrimitivePDTAdapter<MyDuration> {
+        @Override public String typeName() { return "Duration"; }
+        @Override public Class<MyDuration> targetClass() { return 
MyDuration.class; }
+        @Override public String toValue(MyDuration obj) { return "PT" + 
obj.seconds + "S"; }
+        @Override public MyDuration fromValue(String value) {
+            // parse PTnS
+            final String stripped = value.replaceAll("[PTS]", "").replace("H", 
"").replace("M", "");
+            return new MyDuration(Integer.parseInt(stripped));
+        }
+    }
 }

Reply via email to