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

Expanding capabilities of @JsonRootName #662

Open
RutledgePaulV opened this issue Dec 29, 2014 · 4 comments
Open

Expanding capabilities of @JsonRootName #662

RutledgePaulV opened this issue Dec 29, 2014 · 4 comments

Comments

@RutledgePaulV
Copy link

Disclaimer

I'm not sure where to put something like this, so if it should be elsewhere let me know.

Proposal

I would like to propose expansion of the @JsonRootName annotation's usage to allow specifying more than a single level of nesting. In order to avoid creating a custom syntax and remain valid among json keys, I propose the following interface:

public String[] value() default "";

Which would be used like:

@JsonRootName( value = {"root", "results", "resultA"} )
public class ResultA{
    ....
}

With the idea that it would allow the deserialization of:

{
    "root": {
       "results": {
            "resultA": {
                 ...
             }
        }
    }
}

Naive Implementation

The approach I've implemented for my own purposes to extract the relevant piece before reading as a particular class (I'm not suggesting this be the implementation, just giving an example):

 private String extractRelevant(String[] pathSegments, String serialized) throws IOException {
        JsonNode node = objectMapper.readTree(serialized);
        for (String segment : pathSegments) {
            if (StringUtils.isNotBlank(segment)) {
                node = node.get(segment);
            } else {
                return node.toString();
            }
        }
        return node.toString();
    }

Why?

I recently started consuming an API that provides some meta-data around each of the responses that it provides. This information may be useful for them and some more advanced API's, but in reality I'm only interested in consuming the inner contents. I got around this by definining a custom annotation and custom deserializer (that unfortunately couldn't extend JsonDeserializer, until this is released: #165).

There's a strong possibility that there already exists a preferred way to handle this situation and I'm just not aware of it. If that's the case, feel free to direct me to some documentation and close this ticket.

I think this particular approach has the added bonus of being entirely backwards compatible and (I think) pretty simple to implement once these questions are answered:

Questions

  • What's the behavior if the annotation specifies a path that doesn't exist?
  • Can the annotation define an element in the array? What would that look like?
    ex: @JsonRootName( value = {"root", "results[0]"} ), what happens if the element doesn't exist in the array?

Suggestions

The question crossed my mind as to whether this should be extended to field annotations and the like. Common sense quickly said "NO", this should only be used for classes for this kind of usecase, not arbitrary deserialization mappings since that just invites problems.

Anticipated Concerns:

Concern:

it may be considered a deviation from the generally agreed upon best-practice that a model in your application should be the same as the data model being consumed.

Response:

JSON is often used to represent nested object relationships, this just encourages consumption in that manner while decreasing the amount of overhead involved with narrowing a verbose response to the relevant information.

Concern:

@JsonRootName might be a confusing name since using this method, you could technically point to something that isn't really a "root node" of the response, instead it is the "root node" upon which the object should be deserialized.

Response:

Uh, I agree it might be unclear. The reason I suggested extending this interface is because the single-element use case would be no different than the existing behavior. I think it is probably best to add it here.

@cowtowncoder
Copy link
Member

One possibility would be to make use of JSONPointer notation; I have been thinking of adding @JsonWrapped eventually, taking in a JSON Pointer expression. It'd usually be a single element, but could also be multi-element sequence.

@RutledgePaulV
Copy link
Author

I had no idea that existed. Personally, i think i prefer the array since there's no need to learn a new pointer syntax and it feels cleaner to me. However, if you're looking for a common solution for other areas it may be best to go with a standard with a spec. Is pointer notation used elsewhere in jackson already?

@cowtowncoder
Copy link
Member

JSON pointer is minimally simple, really. Just separate names with slashes.
Jackson does support traversal using JSON pointer via JsonNode (method "at()" I think). Class JsonPointer itself is in jackson-core, since one related idea is to allow filtering, to only expose content that matches. This is actually how I would possibly implement deserialization, by sort of hiding non-matching content.

But there is also the other question of wrapping outside of root names. So perhaps one possibility would be to add String-array notation for @JsonRootName, only applicable for actual root, and then separately consider adding general-purpose @JsonWrapped.

@RutledgePaulV
Copy link
Author

That sounds like a good option if you're okay using two approaches, at least that way @JsonRootName would remain backwards compatible. If the pointer syntax is that simple, I'd be totally comfortable with using that, but i guess it's a question of priorities.

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

2 participants