diff --git a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java index ce12aeacd7..7ad4ddc757 100644 --- a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java +++ b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java @@ -899,6 +899,16 @@ ResolvedCodec resolveCodec( JsonTypeResolver localResolver, JsonCodecFactory childFactory, boolean runtimeType) { + return resolveCodec(rawType, typeRef, localResolver, childFactory, runtimeType, false); + } + + ResolvedCodec resolveCodec( + Class rawType, + TypeRef typeRef, + JsonTypeResolver localResolver, + JsonCodecFactory childFactory, + boolean runtimeType, + boolean skipModuleFactories) { JsonValueCodec customCodec = customCodec(rawType); if (customCodec != null) { return new ResolvedCodec(customCodec, null); @@ -944,7 +954,8 @@ ResolvedCodec resolveCodec( if (semanticToken) { // The exact JVM carrier codec cannot erase an explicit semantic type. The installed module // owns that representation even when the semantic value uses a primitive carrier. - ResolvedCodec resolved = createModuleCodec(typeRef, localResolver, runtimeType); + ResolvedCodec resolved = + skipModuleFactories ? null : createModuleCodec(typeRef, localResolver, runtimeType); if (resolved == null) { throw new ForyJsonException("No installed JSON module owns semantic type " + typeRef); } @@ -955,7 +966,8 @@ ResolvedCodec resolveCodec( return new ResolvedCodec(codec, null); } if (rawType == Object.class) { - ResolvedCodec resolved = createModuleCodec(typeRef, localResolver, runtimeType); + ResolvedCodec resolved = + skipModuleFactories ? null : createModuleCodec(typeRef, localResolver, runtimeType); if (resolved != null) { return resolved; } @@ -1009,7 +1021,8 @@ ResolvedCodec resolveCodec( if (Path.class.isAssignableFrom(rawType)) { return new ResolvedCodec(ScalarCodecs.PathCodec.INSTANCE, null); } - ResolvedCodec resolved = createModuleCodec(typeRef, localResolver, runtimeType); + ResolvedCodec resolved = + skipModuleFactories ? null : createModuleCodec(typeRef, localResolver, runtimeType); if (resolved != null) { return resolved; } @@ -1034,6 +1047,10 @@ JsonValueCodec createRuntimeCodec(Class rawType, JsonTypeResolver localRes return resolved == null ? null : resolved.codec; } + boolean hasCodecFactories() { + return codecFactories.length != 0; + } + private JsonValueCodec createExactCodec( Class target, TypeRef typeRef, diff --git a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java index 089e4154a0..aef884a385 100644 --- a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java +++ b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java @@ -541,6 +541,11 @@ private JsonTypeInfo resolveTypeInfo(Type declaredType, Class rawType, Object } private JsonTypeInfo resolveTypeInfo(TypeRef declaredType, Object key) { + return resolveTypeInfo(declaredType, key, false); + } + + private JsonTypeInfo resolveTypeInfo( + TypeRef declaredType, Object key, boolean skipModuleFactories) { validateCovariant(declaredType); Class rawType = declaredType.getRawType(); JsonTypeInfo typeInfo = customTypeInfo(declaredType, rawType); @@ -560,7 +565,7 @@ private JsonTypeInfo resolveTypeInfo(TypeRef declaredType, Object key) { codec.resolveTypes(declaredType, this); return typeInfo; } - return buildTypeInfo(rawType, declaredType, key); + return buildTypeInfo(rawType, declaredType, key, null, skipModuleFactories); } private JsonTypeInfo resolveTypeInfo(TypeRef declaredType, JsonCodec annotation) { @@ -1024,7 +1029,26 @@ private JsonTypeInfo resolveRuntimeTypeInfo(Class runtimeType) { // The default Java object model is the declared schema for this exact class. Reuse that // binding so generated capabilities stay on the direct root path. Only a codec actually // selected by a runtime factory needs the runtime-only publication below. - typeInfo = getTypeInfo(typeRef); + if (!sharedRegistry.hasCodecFactories()) { + typeInfo = getTypeInfo(runtimeType, runtimeType); + } else { + // The runtime factory already declined this root, so the declared fallback must not ask + // the same factory again as if the root were a field occurrence. + ResolutionSnapshot snapshot = beginResolution(); + try { + typeInfo = resolveTypeInfo(typeRef, resolutionTypeKey(typeRef), true); + Object key = resolutionTypeKey(typeRef); + if (typeInfos.get(key) == typeInfo) { + typeInfos.remove(key); + } + completeResolution(snapshot); + } catch (RuntimeException | Error e) { + rollbackResolution(snapshot); + throw e; + } finally { + endResolution(); + } + } runtimeTypeInfos.put(runtimeType, typeInfo); return typeInfo; } @@ -2711,14 +2735,24 @@ private ObjectCodec newObjectCodec(TypeRef ownerType) { } private JsonTypeInfo buildTypeInfo(Class rawType, TypeRef typeRef, Object key) { - return buildTypeInfo(rawType, typeRef, key, null); + return buildTypeInfo(rawType, typeRef, key, null, false); } private JsonTypeInfo buildTypeInfo( Class rawType, TypeRef typeRef, Object key, JsonCodecFactory childFactory) { + return buildTypeInfo(rawType, typeRef, key, childFactory, false); + } + + private JsonTypeInfo buildTypeInfo( + Class rawType, + TypeRef typeRef, + Object key, + JsonCodecFactory childFactory, + boolean skipModuleFactories) { sharedRegistry.checkSecure(rawType); JsonSharedRegistry.ResolvedCodec resolved = - sharedRegistry.resolveCodec(rawType, typeRef, this, childFactory, false); + sharedRegistry.resolveCodec( + rawType, typeRef, this, childFactory, false, skipModuleFactories); if (resolved == null) { return buildObjectTypeInfo(typeRef, key); } diff --git a/java/fory-json/src/test/java/org/apache/fory/json/JsonModuleTest.java b/java/fory-json/src/test/java/org/apache/fory/json/JsonModuleTest.java index 0842c39ca2..ff768d46ea 100644 --- a/java/fory-json/src/test/java/org/apache/fory/json/JsonModuleTest.java +++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonModuleTest.java @@ -59,6 +59,26 @@ import org.testng.annotations.Test; public class JsonModuleTest { + public static class FactoryPet {} + + @Test + public void factoryDoesNotRequeryDeclinedRoot() { + List runtimeTypes = new ArrayList<>(); + JsonCodecFactory factory = + (type, resolver, runtimeType) -> { + if (type.getRawType() == FactoryPet.class) { + runtimeTypes.add(runtimeType); + } + return null; + }; + ForyJson json = + ForyJson.builder().withModule(context -> context.registerCodecFactory(factory)).build(); + + json.toJson(new FactoryPet()); + json.fromJson("{}", FactoryPet.class); + assertEquals(runtimeTypes, Arrays.asList(true, false)); + } + @Test public void installsOnceAndFreezesContext() { AtomicInteger installs = new AtomicInteger();