@@ -19,6 +19,7 @@ import com.embabel.agent.core.DomainType
1919import com.embabel.agent.core.JvmType
2020import com.embabel.common.util.indent
2121import com.fasterxml.jackson.databind.ObjectMapper
22+ import org.slf4j.LoggerFactory
2223import java.lang.reflect.InvocationHandler
2324import java.lang.reflect.Method
2425import java.lang.reflect.Proxy
@@ -78,21 +79,34 @@ interface NamedEntityData : EntityData, NamedEntity {
7879 * This allows hydration even when [linkedDomainType] is not set,
7980 * as long as the entity's labels match the target type and the properties are compatible.
8081 *
81- * @param objectMapper the ObjectMapper to use for deserialization
82+ * For interface types, a dynamic proxy is created.
83+ * For concrete classes, Jackson deserialization is used.
84+ *
85+ * @param objectMapper the ObjectMapper to use for deserialization (only for concrete classes)
8286 * @param type the target class to hydrate to
8387 * @return the hydrated instance, or null if hydration fails
8488 */
89+ @Suppress(" UNCHECKED_CAST" )
8590 fun <T : NamedEntity > toTypedInstance (objectMapper : ObjectMapper , type : Class <T >): T ? {
8691 return try {
87- // Build the full property map including id, name, description
88- val allProperties = buildMap {
89- put(" id" , id)
90- put(" name" , name)
91- put(" description" , description)
92- putAll(properties)
92+ if (type.isInterface) {
93+ // Use dynamic proxy for interfaces
94+ toInstance(type) as T
95+ } else {
96+ // Use Jackson for concrete classes
97+ val allProperties = buildMap {
98+ put(" id" , id)
99+ put(" name" , name)
100+ put(" description" , description)
101+ putAll(properties)
102+ }
103+ objectMapper.convertValue(allProperties, type)
93104 }
94- objectMapper.convertValue(allProperties, type)
95105 } catch (e: Exception ) {
106+ LoggerFactory .getLogger(type).warn(
107+ " Failed to hydrate NamedEntityData (id=$id ) to type ${type.name} : ${e.message} " ,
108+ e
109+ )
96110 null
97111 }
98112 }
@@ -179,6 +193,7 @@ internal class NamedEntityInvocationHandler(
179193 val indent = args?.getOrNull(1 ) as ? Int ? : 0
180194 entityData.infoString(verbose, indent)
181195 }
196+
182197 " propertiesToPersist" -> entityData.propertiesToPersist()
183198
184199 // Kotlin property getter pattern: getXxx() -> property "xxx"
0 commit comments