You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Take the above example. We define a TaskDef 'greet' using the method 'greeting'. We map a Java class to an LH VariableType three times:
We define that the TaskDef with have an input variable String name in the method definition. Behind the scenes, sdk-java code checks the class of that input variable and sorts it into one of the following VariableType categories: STR, INT, DOUBLE, BOOL, BYTES, JSON_OBJ, JSON_ARR.
We define that the TaskDef will output a String in the method definition. Behind the scenes, sdk-java code checks the class of that return type and sorts it into one of the aforementioned VariableType categories.
We return an instance of an object, in this case a String"hello there, " + name. Behind the scenes, sdk-java code checks the class of that object and sorts it into one of the aforementioned VariableType categories.
This works well when the Java Class has a matching LH VariableType, like how in the above examples String would always map to VariableType.STR.
However, we run into some problems when there isn't a perfect mapping between the Java Class and the LH VariableType...
Take the above example, where we defined a TaskDef that takes in a String username and returns a Java UUID object. The server handles the input just fine, it maps String to STR. Things fall apart quickly when we take a look at the declared Return Type and the Returned Object at runtime.
Mapping the Return Type
The return type gets mapped to a VariableType by the following sdk-java method 'javaClassToLHVarType':
publicstaticVariableTypejavaClassToLHVarType(Class<?> cls) {
if (Integer.class.isAssignableFrom(cls)
|| Long.class.isAssignableFrom(cls)
|| int.class.isAssignableFrom(cls)
|| long.class.isAssignableFrom(cls)) returnVariableType.INT;
if (Double.class.isAssignableFrom(cls)
|| Float.class.isAssignableFrom(cls)
|| double.class.isAssignableFrom(cls)
|| float.class.isAssignableFrom(cls)) returnVariableType.DOUBLE;
if (String.class.isAssignableFrom(cls)) returnVariableType.STR;
if ((Boolean.class.isAssignableFrom(cls) || boolean.class.isAssignableFrom(cls))) returnVariableType.BOOL;
if (byte[].class.isAssignableFrom(cls)) returnVariableType.BYTES;
if (List.class.isAssignableFrom(cls)) returnVariableType.JSON_ARR;
returnVariableType.JSON_OBJ;
}
The java.util.UUID class implements java.io.Serializable and Comparable<UUID>. So it doesn't match our conditions for an Integer, a Double, a String, a Boolean, a byte[], nor a List. Since it didn't pass any of the aforementioned cases, our UUID gets mapped into the JSON_OBJVariableType.
Mapping the Returned Object at Runtime
When we run the Task Method and it returns a UUID object, we cast the object to a compatible VariableType. The casting is performed by the objToVarVal method:
publicstaticVariableValueobjToVarVal(Objecto) throwsLHSerdeError {
if (oinstanceofVariableValue) return (VariableValue) o;
VariableValue.Builderout = VariableValue.newBuilder();
if (o == null) {
// nothing to do
} elseif (Long.class.isAssignableFrom(o.getClass())) {
out.setInt((Long) o);
} elseif (Integer.class.isAssignableFrom(o.getClass())) {
out.setInt((Integer) o);
} elseif (Double.class.isAssignableFrom(o.getClass())) {
out.setDouble((Double) o);
} elseif (Float.class.isAssignableFrom(o.getClass())) {
out.setDouble((Float) o);
} elseif (oinstanceofString) {
out.setStr((String) o);
} elseif (oinstanceofBoolean) {
out.setBool((Boolean) o);
} elseif (oinstanceofbyte[]) {
out.setBytes(ByteString.copyFrom((byte[]) o));
} else {
// At this point, all we can do is try to make it a JSON type.JsonResultjsonResult = LHLibUtil.serializeToJson(o);
switch (jsonResult.getType()) {
caseARRAY:
out.setJsonArr(jsonResult.getJsonStr());
break;
caseOBJECT:
out.setJsonObj(jsonResult.getJsonStr());
break;
casePRIMITIVE:
// Trims the quotes off the stringout.setStr(jsonResult
.getJsonStr()
.substring(1, jsonResult.getJsonStr().length() - 1));
break;
caseNULL:
break;
default:
thrownewLHSerdeError("Failed serializing object to Json: " + o.toString());
}
}
returnout.build();
}
Similarly to the return type conditions, our UUID object is not an instance of the following Java classes: Long, Integer, Double, String, Boolean, and byte[]. However, once we make it past those conditions, we still need to cast the object into a compatible format or we can't send it to the server at all. So we try to cast it using GSON (previously Jackson, but the same outcome applies). GSON does some magic behind the scenes and casts the UUID to a String, since the UUID object doesn't neatly serialize into any other JSON types.
Different Results
Our Return TypeUUID mapped to the VariableTypeJSON_OBJ
Our Returned Object, an instance of UUID, mapped to the VariableTypeSTR.
JSON_OBJ != STR
The crux of the problem is that our two methods for mapping from the Java domain to the LittleHorse domain are different. The first method, javaClassToLHVarType, maps our UUID class to a JSON_OBJ, since it doesn't match any of the other conditions. Our second method, objToVarVal, casts our UUID object to a STR, since the UUID object doesn't map into a JSON_OBJ nicely.
What is the solution?
My knee-jerk reaction to this is to unite the two mapping implementations.
It is incredibly difficult to cast UUID objects to a JSON_OBJ neatly. It is hard to articulate all the unique cases that make this difficult, which is why our current solution casts them to STRs. So changing the mapping implementation for objToVarVal would be difficult.
If we were to change the Return Type mapping solution to match objToVarVal, we run into a similar set of problems. How do we map a UUID to a STR in a way that respects other unique cases such as Currency, Date, and Calendar. We also need to make sure that users can still define POJOs (Plain Old Java Objects) that map into JSON_OBJ still.
I am not sure what the solution here is. Maybe we should reduce the amount of magic mappings going on behind the scenes, and allow users to declare their own Serializers and Deserializers for non-standard class types.
serverAffects the core LH Server code.sdk-javaAffects the Java SDK (not all clients).
1 participant
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Background
In our Java SDK, we often perform an operation where we try to map a Java Class to an LH
VariableType
:Take the above example. We define a
TaskDef
'greet' using the method 'greeting'. We map a Java class to an LHVariableType
three times:TaskDef
with have an input variableString name
in the method definition. Behind the scenes,sdk-java
code checks the class of that input variable and sorts it into one of the followingVariableType
categories:STR
,INT
,DOUBLE
,BOOL
,BYTES
,JSON_OBJ
,JSON_ARR
.TaskDef
will output aString
in the method definition. Behind the scenes,sdk-java
code checks the class of that return type and sorts it into one of the aforementionedVariableType
categories.String
"hello there, " + name
. Behind the scenes,sdk-java
code checks the class of that object and sorts it into one of the aforementionedVariableType
categories.This works well when the Java Class has a matching LH
VariableType
, like how in the above examplesString
would always map toVariableType.STR
.However, we run into some problems when there isn't a perfect mapping between the Java Class and the LH
VariableType
...Problem
Take the above example, where we defined a
TaskDef
that takes in aString username
and returns a JavaUUID
object. The server handles the input just fine, it mapsString
toSTR
. Things fall apart quickly when we take a look at the declared Return Type and the Returned Object at runtime.Mapping the Return Type
The return type gets mapped to a
VariableType
by the followingsdk-java
method 'javaClassToLHVarType':The
java.util.UUID
class implementsjava.io.Serializable
andComparable<UUID>
. So it doesn't match our conditions for anInteger
, aDouble
, aString
, aBoolean
, abyte[]
, nor aList
. Since it didn't pass any of the aforementioned cases, ourUUID
gets mapped into theJSON_OBJ
VariableType
.Mapping the Returned Object at Runtime
When we run the Task Method and it returns a
UUID
object, we cast the object to a compatibleVariableType
. The casting is performed by theobjToVarVal
method:Similarly to the
return type
conditions, ourUUID
object is not an instance of the following Java classes:Long
,Integer
,Double
,String
,Boolean
, andbyte[]
. However, once we make it past those conditions, we still need to cast the object into a compatible format or we can't send it to the server at all. So we try to cast it usingGSON
(previously Jackson, but the same outcome applies).GSON
does some magic behind the scenes and casts theUUID
to aString
, since theUUID
object doesn't neatly serialize into any other JSON types.Different Results
Our
Return Type
UUID
mapped to theVariableType
JSON_OBJ
Our
Returned Object
, an instance ofUUID
, mapped to theVariableType
STR
.JSON_OBJ
!=STR
The crux of the problem is that our two methods for mapping from the Java domain to the LittleHorse domain are different. The first method,
javaClassToLHVarType
, maps ourUUID
class to aJSON_OBJ
, since it doesn't match any of the other conditions. Our second method,objToVarVal
, casts ourUUID
object to aSTR
, since theUUID
object doesn't map into aJSON_OBJ
nicely.What is the solution?
My knee-jerk reaction to this is to unite the two mapping implementations.
It is incredibly difficult to cast
UUID
objects to aJSON_OBJ
neatly. It is hard to articulate all the unique cases that make this difficult, which is why our current solution casts them toSTR
s. So changing the mapping implementation forobjToVarVal
would be difficult.If we were to change the
Return Type
mapping solution to matchobjToVarVal
, we run into a similar set of problems. How do we map aUUID
to aSTR
in a way that respects other unique cases such asCurrency
,Date
, andCalendar
. We also need to make sure that users can still definePOJO
s (Plain Old Java Objects) that map intoJSON_OBJ
still.I am not sure what the solution here is. Maybe we should reduce the amount of magic mappings going on behind the scenes, and allow users to declare their own
Serializer
s andDeserializer
s for non-standard class types.Beta Was this translation helpful? Give feedback.
All reactions