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

Customizing untyped object deserialization #89

Closed
tberman opened this issue Jul 3, 2013 · 10 comments
Closed

Customizing untyped object deserialization #89

tberman opened this issue Jul 3, 2013 · 10 comments
Milestone

Comments

@tberman
Copy link

tberman commented Jul 3, 2013

So, I am using the scala module, and parsing json that looks like:

{ "type": "blah", "data": { "key": "value" } }

The outer object I am parsing directly into a Map[String, Any] which is working fine. However, the "data" object ends up being a JMapWrapper instead of a Map[String, Any] as I would expect.

What am I missing?

@christophercurrie
Copy link
Member

What you're running into is the fact that the underlying Jackson system processes values in a narrow context. At the top level, you've told the deserializer you want a Scala Map[String, Any], and so it obliges. Buy when it process the values, all it has is the "Any" type, and the fact that it's reading a JSON Object. It doesn't "remember" that you wanted a particular type at the root that would work. 

The logic for selecting the type to use in this case is done by (IIRC) UntypedObjectDeserializer. This exists to improve on the old behavior that retuned a java.lang.Map implementation that is Jackson's default. As it stands, you are getting a Scala collection.Map, it just happens to be a JMapWrapper instead of a scala.Predef.Map.


It might be nice if you could, for example, specify TreeMap at the root and know that all of the Any's you get back that are objects would be tree maps, but at this point, most Jackson users don't work this way. 

Those that need untyped data will serialize instead to JsonNode, which has some advantages over maps. Those that want type checking will define (case) classes that model the data set they are trying to parse.

If you feel strongly that consistent Map typing is important to your use case in some way, let me know a little about why it matters, and I'll see what might be done.

@tberman
Copy link
Author

tberman commented Jul 3, 2013

So, the main value for me is being able to write code that operates on Maps generically and not have to worry about JMapWrapper[String, Any] vs Map[String, Any] or even worse, writing code that assumes everything is going to be MapLike[String, Any, MapLike[String, Any]].

Imagine a function that handles some data structure that can either be inside a list or standalone from a client. Right now, that function needs to handle both JMapWrapper (if it comes from the list) and Map[String, Any] if it comes directly. It also exposes an implementation detail to generic code that can take data from a database (which is going to come back as a Map) vs from JSON.

The solution may be instead (or perhaps additionally to) assuming the root object should be used for any map-like Anys that there is some way in the ObjectMapper to specify what type you would like for array-like and map-like objects.

Does that make sense?

@tberman
Copy link
Author

tberman commented Jul 3, 2013

(btw, switching to using collection.Map as the common descendant made the code nicer, but it would still be nice to fix this generically)

@cowtowncoder
Copy link
Member

Not sure if this helps, but it is possible to instruct Jackson with respect to which Map or Collection to use (abstract -to concrete mapping). This has been used by some users to default to TreeMap instead of HashMap for example.
This is global setting, and Scala module's interaction could change things too. But I thought I will mention just in case it could help.

@tberman
Copy link
Author

tberman commented Jul 4, 2013

Oh, how do you do that?

@christophercurrie
Copy link
Member

@cowtowncoder, the feature being requested isn't exactly what you're describing. IIUC, you are describing the feature that, given a Bean class with a property of type java.util.Map, allows the end user to configure which Map implementation it gets.

To put it in Java terms, what @tberman is asking for is, when deserializing arbitrary JSON to java.util.HashMap<String,Object>, to be able to specify what type is used for deserializing JSON Object nodes. In this case Jackson is, I believe, returning a java.util.LinkedHashMap<String, Object>, and this choice is not configurable. This used to also be true for the Scala module, but issue #12 asked for the default type to be a Scala collection and not a Java one, so I added a small piece of code to wrap them using the Scala provided wrapper classes.

As far as it goes, I feel that those facts are an implementation detail, and that the only guarantees (albeit undocumented) the module gives is to return some implementation of scala.collection.Map. The fact that you can do type introspection to determine which implementation is returned doesn't break the effective guarantee.

Given that, and given that Jackson has other, better methods to support stronger typing, I'm probably not going to implement any configuration for this in the near future, unless the databind module were to add similar support for Java. But I'll leave the issue open for the sake of documentation, in case someone wants to submit a patch. :)

@cowtowncoder
Copy link
Member

Right, I realize that it is not exact match; but what I was suggesting was that perhaps ability to change the default impl could resolve the issue. However, I am guessing it wouldn't since it has to do with wrapping.

@christophercurrie
Copy link
Member

Well, it might; I hadn't yet searched the code for the functionality you're describing, and I (incorrectly, it seems) guessed that it was a specific override setting for JDK Collection/Map types. But now I suspect you're talking about AbstractTypeResolver which, now that I know it exists, makes a lot of sense. (Do correct me if I'm looking at the wrong thing).

The Scala module could check the AbstractTypeResolvers to find a mapping for scala.collection.Map, and use it if it exists (or predefine one that could be overridden). It even sounds like something that would be useful to go into databind's UntypedObjectDeserializer, in a way that both the Java and the Scala code to use. Ok, I'll take a look and see how straightforward this might be to do.

@cowtowncoder
Copy link
Member

You are right, I was specifically referring to AbstractTypeResolver, and convenience wrappers via SimpleModule.

And I think your thinking along lines of Scala module registering its own resolver could make sense. If all goes well, generic types should work too (that is, not losing parameterization of Map(Like)s, Collection(Like)s).

UntypedObjectDeserializer... good point, that isn't connected, but it really should.
I will file a bug for that, since it definitely should obey these settings. Not sure how to do it efficiently, but it seems like an actual omission for sure.

@christophercurrie
Copy link
Member

I've added support for recognizing AbstractTypeResolvers. The test added in 8421eba should demonstrate how to use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants