-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix serialization of subclasses #92
Fix serialization of subclasses #92
Conversation
8faaf8c
to
988a10e
Compare
This is the reproduction of an error reported by a user. Because StepError contains additional fields to a generic Exception, deserialization fails.
This shows that the issue is not limited to StepError/Exception.
Previously we were deserializing step data based on the T provided by the compiler. However, this could be lossy if the step data was actually a subclass of T with additional fields and is semantically incorrect if we try to deserialize it as the parent class. To remedy this, we need to know what the actual class of the step data was before it was memoized with the Inngest server. We are adding this as an additional field `class` on the JSON when serializing, then reading (and deleting) this field and using it with reflection to deserialize using the proper class. It might make sense to put the field `class` in some kind of meta/extension section of the payload to avoid conflict, but it's highly unlikely a field will legitimately be named class in the user code since it is a reserved keyword in Java. This strategy doesn't entirely work for schema evolution because it's still possible the classes have changed to the point where deserialization would fail. We should follow up here and probably have a fallback mechanism of returning a generic hash if deserialization fails.
a317066
to
644a99e
Compare
|
||
val writeableJson = serializedStepData as ObjectNode | ||
val className = writeableJson.remove("class").asText() | ||
return mapper.treeToValue(writeableJson, Class.forName(className)) as T |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to figure out how to handle the case where this will fail because the class no longer exists (or renamed) or has been changed enough since serialization that this won't deserialize
val jsonString = mapper.writeValueAsString(stepData) | ||
val readOnlyJson = mapper.readTree(jsonString) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
converting an object to a JSON string only to read that back in to get a JSON node is silly, but replacing this with
val writeableJson = mapper.convertValue(stepData, ObjectNode::class.java)
resulted in a bunch of test failures, so we decided to punt on figuring this out to a follow up
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good - I think I may have had code like this in an early PoC. There wasn't a great pattern for this in Java and the JSON libraries from what I could tell that didn't result in duplicate parsing/serialization work. I'm find with leaving this for a future improvement.
Summary
Changes:
StepError
as a genericException
Checklist
Update documentationRelated
https://inngest.slack.com/archives/C06KB85RXRV/p1728067438991139