Skip to content

Commit 057b121

Browse files
committed
Fix deserialization with custom addition
- try-catch based approach on Subtype annotation mentions - Also pass custom list of candidates
1 parent 986b222 commit 057b121

File tree

3 files changed

+101
-0
lines changed

3 files changed

+101
-0
lines changed

orchestration/src/main/java/com/sap/ai/sdk/orchestration/JacksonMixins.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@ interface LLMModuleResultMixIn {}
1818
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
1919
@JsonDeserialize(as = LLMChoice.class)
2020
interface ModuleResultsOutputUnmaskingInnerMixIn {}
21+
22+
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
23+
interface NoneTypeInfoMixin {}
24+
2125
}

orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import com.fasterxml.jackson.core.JsonProcessingException;
66
import com.fasterxml.jackson.databind.JsonNode;
77
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import com.fasterxml.jackson.databind.module.SimpleModule;
89
import com.fasterxml.jackson.databind.node.ObjectNode;
910
import com.google.common.annotations.Beta;
1011
import com.sap.ai.sdk.core.AiCoreService;
1112
import com.sap.ai.sdk.core.DeploymentResolutionException;
1213
import com.sap.ai.sdk.core.commons.ClientResponseHandler;
14+
import com.sap.ai.sdk.orchestration.model.ChatMessagesInner;
1315
import com.sap.ai.sdk.orchestration.model.CompletionPostRequest;
1416
import com.sap.ai.sdk.orchestration.model.CompletionPostResponse;
1517
import com.sap.ai.sdk.orchestration.model.LLMModuleResult;
@@ -47,6 +49,14 @@ public class OrchestrationClient {
4749
JACKSON.addMixIn(
4850
ModuleResultsOutputUnmaskingInner.class,
4951
JacksonMixins.ModuleResultsOutputUnmaskingInnerMixIn.class);
52+
53+
var module =
54+
new SimpleModule()
55+
.addDeserializer(
56+
ChatMessagesInner.class,
57+
new PolymorphicFallbackDeserializer<>(ChatMessagesInner.class))
58+
.setMixInAnnotation(ChatMessagesInner.class, JacksonMixins.NoneTypeInfoMixin.class);
59+
JACKSON.registerModule(module);
5060
}
5161

5262
@Nonnull private final Supplier<HttpDestination> destinationSupplier;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.sap.ai.sdk.orchestration;
2+
3+
import com.fasterxml.jackson.annotation.JsonSubTypes;
4+
import com.fasterxml.jackson.core.JsonParser;
5+
import com.fasterxml.jackson.databind.DeserializationContext;
6+
import com.fasterxml.jackson.databind.JsonMappingException;
7+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
8+
import java.io.IOException;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
12+
/**
13+
* Handles polymorphic deserialization for a base class or interface.
14+
*
15+
* <p>This deserializer attempts to deserialize JSON into a subtype of a base class or interface.
16+
* Subtypes are either discovered using the {@link JsonSubTypes} annotation or provided explicitly.
17+
* If deserialization fails for all candidates, a {@link JsonMappingException} is thrown with
18+
* suppressed exceptions.
19+
*
20+
* @param <T> The base type for deserialization.
21+
*/
22+
public class PolymorphicFallbackDeserializer<T> extends StdDeserializer<T> {
23+
24+
private final List<Class<? extends T>> candidates;
25+
26+
/**
27+
* Constructs the deserializer using the {@link JsonSubTypes} annotation.
28+
*
29+
* @param baseClass The base class or interface to be resolved.
30+
* @throws IllegalStateException If no subtypes are found.
31+
*/
32+
protected PolymorphicFallbackDeserializer(Class<T> baseClass) {
33+
super(baseClass);
34+
35+
final var subTypes = baseClass.getAnnotation(JsonSubTypes.class);
36+
if (subTypes == null || subTypes.value().length == 0) {
37+
throw new IllegalStateException("No subtypes found for " + baseClass.getName());
38+
}
39+
40+
candidates = new ArrayList<>();
41+
for (final var subType : subTypes.value()) {
42+
candidates.add((Class<? extends T>) subType.value());
43+
}
44+
}
45+
46+
/**
47+
* Constructs the deserializer with an explicit list of candidate types.
48+
*
49+
* @param baseClass The base class or interface to be resolved.
50+
* @param candidates A list of candidate classes to try deserialization.
51+
*/
52+
protected PolymorphicFallbackDeserializer(
53+
Class<T> baseClass, List<Class<? extends T>> candidates) {
54+
super(baseClass);
55+
this.candidates = candidates;
56+
}
57+
58+
/**
59+
* Deserializes the JSON into the first matching candidate type.
60+
*
61+
* @param jsonParser The parser providing the JSON.
62+
* @param deserializationContext The deserialization context.
63+
* @return The deserialized object of a matching candidate type.
64+
* @throws JsonMappingException If deserialization fails for all candidates.
65+
* @throws IOException If an I/O error occurs.
66+
*/
67+
@Override
68+
public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
69+
throws IOException {
70+
71+
final var root = jsonParser.readValueAsTree();
72+
final var throwable =
73+
JsonMappingException.from(
74+
jsonParser,
75+
"PolymorphicFallbackDeserializer failed to deserialize " + handledType().getName());
76+
77+
for (final var candidate : candidates) {
78+
try {
79+
return jsonParser.getCodec().treeToValue(root, candidate);
80+
} catch (JsonMappingException e) {
81+
throwable.addSuppressed(e);
82+
}
83+
}
84+
85+
throw throwable;
86+
}
87+
}

0 commit comments

Comments
 (0)