Skip to content
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

Is there a way to avoid ClassCastException without using @type fields on a JSON object? #150

Closed
bmbanker opened this issue Oct 26, 2020 · 8 comments
Labels

Comments

@bmbanker
Copy link
Contributor

bmbanker commented Oct 26, 2020

If I attempt to deserialize JSON that was serialized with the default JsonWriter type settings, I can cast it to a typed object successfully. However, if I attempt to deserialize JSON that was serialized with JsonWriter.TYPE = false, I receive a ClassCastException.

Is there any way to avoid this without needing to rely on the @type parameters? The reason I'm asking is because the objects I use are quite complex, and there's a lot of @type data being sent. Not serializing @type parameters can help me save ~3kB per request on smaller objects in my system (~6kB per round-trip).

Perhaps a solution would be new implementation of method JsonReader.readObject(Class) that would make a best attempt at deserialization without any @type parameters? Using generics, theoretically it could recursively walk the JsonObject structure and attempt to match the value up with the values of declared Fields on the Class object given, and you could avoid infinite recursion by using the already inbuilt @ref and @id tags. These are just my first thoughts on a solution, maybe you have something more elegant in mind.

Below is the deserialization code, and the full exception stack trace.

JsonReader jsonReader = new JsonReader(request.getInputStream(), DBObjectServletUtils.jsonObjectOptions);
object = (T) jsonReader.readObject();
java.lang.ClassCastException: com.cedarsoftware.util.io.JsonObject cannot be cast to com.safeharbormonitoring.mgmt.backend.AbstractDBObject
	at com.safeharbormonitoring.mgmt.api.DBObjectServlet.doPut(DBObjectServlet.java:216)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:663)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:668)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Unknown Source)
@jdereg
Copy link
Owner

jdereg commented Oct 27, 2020

If you turn of the @type, then it is best to read the JSON into JsonReader using the API that returns a "Map of Maps" which will prevent any ClassCastExceptions. Here is an example in Groovy:

// shown using Groovy short-hand for Map of options.  See options below.
String json = // some JSON obtained from wherever
Object obj = JsonReader.jsonToJava(json, [(JsonReader.USE_MAPS): true]) 

@bmbanker
Copy link
Contributor Author

bmbanker commented Oct 27, 2020

Is there a challenge or a reason not to create functionality to serialize into a typed object (not a map of maps) without storing type info in the JSON (if you are given the class information as a parameter of the method)?

@jdereg
Copy link
Owner

jdereg commented Oct 31, 2020 via email

@bmbanker
Copy link
Contributor Author

bmbanker commented Oct 31, 2020

I can see how this would be an issue. However, I'm trying to basically skip using a "DTO" entirely. I've built a front end that knows how to interact with unflattened objects, and that's why I'm using your library (because it can handle nested objects with circular dependencies.).

In the scenario provided above, it looks like you are not giving a class parameter to your deserialization function. If you had the same given JSON string, but instead had the below class definitions:

class A {
 String name;
 String lastname;
}

class B {
 A field1;
 Boolean field2;
}

A function JsonReader.jsonToJava(B.class, json); should be able to serialize the JSON into an instance of class B, right?

And if instead the definition of class B looked like this:

class B {
 Object field1;
 Boolean field2;
}

You could either throw an exception (which is what I would recommend), or fill a plan Object with your best guess as to what the types of the children should be (which I definitely wouldn't recommend).

@ozmium
Copy link

ozmium commented Apr 10, 2021

This issue is very similar to issue #122

@kpartlow
Copy link
Contributor

kpartlow commented Nov 9, 2023

I think that is reasonable. We could add an api to provide a hint for the type on the root object and use that to build to build the structure. Whatever type you are typecasting to would be the one we use as your model object at the root level. Everything below that would be matched to field types from that object.

public static <T> T toObjects(InputStream input, ReadOptions options, Class<T> hint)

@jdereg
Copy link
Owner

jdereg commented Nov 9, 2023 via email

@jdereg jdereg added the fixed label Jul 19, 2024
@jdereg
Copy link
Owner

jdereg commented Jul 19, 2024

Root type was added to json-io a while back. That does eliminate the @type at the root level. And again, for all cases where it can be inferred, it is not emitted (that is the default option). Only when the type is ambiguous, is @type emitted. Also, you have the option to not include @type at all, in which case, you will get Map instances.

Closing the issue as fixed (supported) now.

@jdereg jdereg closed this as completed Jul 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants