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

Tree Model (JsonNode) with global typing #353

Closed
cemo opened this issue Nov 26, 2013 · 12 comments
Closed

Tree Model (JsonNode) with global typing #353

cemo opened this issue Nov 26, 2013 · 12 comments
Milestone

Comments

@cemo
Copy link

cemo commented Nov 26, 2013

I am trying to persist my spring security based entities to database. I need to serialize Session Attributes Map<String, Object> to database correctly by preserving their id infos. I had enabled object mapper as this: mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.NON_FINAL, "@class"); . However when I tried to deserialize, I am having an issue.

com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property '@class' that is to contain type id  (for class com.fasterxml.jackson.databind.JsonNode)
 at [Source: java.io.StringReader@b240236; line: 1, column: 193]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
    at com.fasterxml.jackson.databind.DeserializationContext.wrongTokenException(DeserializationContext.java:784)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:147)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:95)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromAny(AsPropertyTypeDeserializer.java:165)

I can actually see @class in my output but still causing an error.
{"@class":"com.fasterxml.jackson.databind.jsontype.TestDefaultForObject$SavedCookie","name":"Cemo","value":"a","comment":"co","domain":"do","maxAge":12,"path":"/asd","secure":true,"version":12}

I started to consider that there is little bug. You can also check yourself too.

 public class SavedCookie implements Serializable {
      private final java.lang.String name;
      private final java.lang.String value;
      private final java.lang.String comment;
      private final java.lang.String domain;
      private final int maxAge;
      private final java.lang.String path;
      private final boolean secure;
      private final int version;

      public SavedCookie(String name, String value, String comment, String domain, int maxAge, String path, boolean secure, int version) {
         this.name = name;
         this.value = value;
         this.comment = comment;
         this.domain = domain;
         this.maxAge = maxAge;
         this.path = path;
         this.secure = secure;
         this.version = version;
      }

      public String getName() {
         return name;
      }

      public String getValue() {
         return value;
      }

      public String getComment() {
         return comment;
      }

      public String getDomain() {
         return domain;
      }

      public int getMaxAge() {
         return maxAge;
      }

      public String getPath() {
         return path;
      }

      public boolean isSecure() {
         return secure;
      }

      public int getVersion() {
         return version;
      }
   }


   public class SavedCookieDeserializer extends JsonDeserializer<SavedCookie> {
      @Override
      public SavedCookie deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
         ObjectCodec oc = jsonParser.getCodec();
         JsonNode node = oc.readTree(jsonParser);
         return new SavedCookie( node.get("name") != null ? node.get("name").asText() : null,
                                 node.get("value") != null ? node.get("value").asText() : null,
                                 node.get("comment") != null ? node.get("comment").asText() : null,
                                 node.get("domain") != null ? node.get("domain").asText() : null,
                                 node.get("maxAge") != null ? node.get("maxAge").asInt() : 0,
                                 node.get("path") != null ? node.get("path").asText() : null,
                                 node.get("secure") != null && node.get("secure").asBoolean(),
                                 node.get("version") != null ? node.get("version").asInt() : 0);
      }
   }

    public void testIssue() throws Exception
    {
       ObjectMapper mapper = new ObjectMapper();

      mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.NON_FINAL, "@class");

       SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null, "TEST", "TEST"));
       testModule.addDeserializer(SavedCookie.class, new SavedCookieDeserializer());
       mapper.registerModule(testModule);

       SavedCookie savedCookie = new SavedCookie("Cemo", "a", "co", "do", 12, "/asd", true, 12);
       // persist at db
       String x = mapper.writeValueAsString(savedCookie);
       // read from db
       SavedCookie o = mapper.reader(SavedCookie.class).readValue(x);
    }
@cowtowncoder
Copy link
Member

Ok thanks. I'll have to see what happens: although polymorphic types should not really apply to JSON Tree Model (it is only for POJOs; and Tree Model is json-specific, untyped model), it should be possible be possible to make things work well enough together.

@cowtowncoder
Copy link
Member

Also: I assume this can be reproduced with 2.3.0.

@cemo
Copy link
Author

cemo commented Nov 26, 2013

Yes, I have checked at latest master.

@tkvangorder
Copy link

Running into the same issue. Any resolution in sight?

@cowtowncoder
Copy link
Member

Hmmh. Looking at example, it is much more involved than I realized -- and combination of custom deserializer with polymorphic types can be challenging.
I'll try to have a look.

@cemo One quick note: with polymorphic types, instead of "deserialize()", the method to (also) override is deserializeWithType(), so this would cause problems for sure.
I think simple correct implementation would be:

    @Override
    public SavedCookie deserializeWithType(JsonParser jp, DeserializationContext ctxt,
            TypeDeserializer typeDeserializer)
        throws IOException, JsonProcessingException
    {
        return (SavedCookie) typeDeserializer.deserializeTypedFromObject(jp, ctxt);
    }

but default from JsonDeserializer might actually also work fine.

@cowtowncoder
Copy link
Member

Another minor improvement suggestion -- not related to the issue itself, but maybe useful: instead of:

node.get("name") != null ? node.get("name").asText() : nul

can do:

node.path("name").textValue()

where 'path()' never returns null (but gives 'MissingNode' if no property exists); and 'textValue()', conversely, does return null for non-text nodes (including MissingNode).

@cowtowncoder
Copy link
Member

Ok. Unless I am mistaken, this is fixed for 2.4 (master branch), but not 2.3 branch. This is (as I recall) due to refactoring that could only be made in master.
Tests pass for master, fail for 2.3, but if anyone can verify that'd be good.

@cowtowncoder cowtowncoder added this to the 2.4.0 milestone Mar 14, 2014
cowtowncoder added a commit that referenced this issue Mar 14, 2014
@cowtowncoder
Copy link
Member

Assume it is fixed with 2.4; possibly due to a fix for #88.

@tkvangorder
Copy link

I just tried it with the 2.4 snapshot and I am happy to report that valueToTree appears to be working! thanks.

However, now on the server side, the jsonrpc4j code is trying to go the other direction tree to object via "readTree" and appears to be getting a similar error:

  2014-03-14 13:33:40,412 DEBUG - Could not complete request
com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property '@type' that is to contain type id  (for class com.fasterxml.jackson.databind.JsonNode)
 at [Source: com.googlecode.jsonrpc4j.NoCloseInputStream@5c81f11f; line: 1, column: 186]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:163)
    at com.fasterxml.jackson.databind.DeserializationContext.wrongTokenException(DeserializationContext.java:648)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:138)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:88)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromAny(AsPropertyTypeDeserializer.java:156)
    at com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer.deserializeWithType(JsonNodeDeserializer.java:141)
    at com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer.deserializeWithType(JsonNodeDeserializer.java:15)
    at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:36)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2577)
    at com.fasterxml.jackson.databind.ObjectMapper.readTree(ObjectMapper.java:1500)
    at com.googlecode.jsonrpc4j.JsonRpcServer.handle(JsonRpcServer.java:224)
    at com.googlecode.jsonrpc4j.JsonRpcServer.handle(JsonRpcServer.java:207)
    at com.googlecode.jsonrpc4j.spring.JsonServiceExporter.handleRequest(JsonServiceExporter.java:40)
    at org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter.handle(HttpRequestHandlerAdapter.java:51)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:945)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:876)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:643)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:723)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:861)
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:606)
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
    at java.lang.Thread.run(Thread.java:724)

@cowtowncoder
Copy link
Member

@snowtoad2 Thank you for quick verification of the orgiinal problem. Could you please file a new bug for the remaining problem? This way it is easier to keep track of what has been fixed, what hasn't.
Also, if possible, a piece of test code (or unit test) would be useful as original snippets now pass.

@tkvangorder
Copy link

I was working on a test case and discovered I still had the older version of jackson on my json rpc server. Once I swapped that for the snapshot everything is now working! So I will not be opening up a new issue. 8)

Thanks for the quick turn-around.

@cowtowncoder
Copy link
Member

Good, thank you for verifying this!

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