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

Support for non-scalar ObjectId Reference deserialiazation #622

Closed
redben opened this issue Nov 19, 2014 · 12 comments
Closed

Support for non-scalar ObjectId Reference deserialiazation #622

redben opened this issue Nov 19, 2014 · 12 comments
Milestone

Comments

@redben
Copy link

redben commented Nov 19, 2014

I am using JSOG by @stickfigure which works beautifully for serialization, but there seems to be an issue with deserialiazation since only scalar values are supported for ObjectIds. It would really be helpfull if this could work.

Below are suggestions from Tatu on the user-list:

The reason it does not work is simple: BeanDeserializer has to use heuristics to know whether value it is looking at might be an object reference [..] Only scalars can be object ids from its perspective.
[...] but maybe, just maybe, we could limit this, to only checking the name of the very first property within Object? If JSOG uses single-property ("$ref") objects -- which I think it does? -- perhaps it is possible to change things such that this detection could work.

and

One quick comment: you can actually globally enable this style of identity inclusion by a custom AnnotationIntrospector -- it can claim(*) that @JsonIdentityInfo was found. If JSOG is registered as a module you can also register such an introspector (to augment whatever default one existed).

(*)I am not familiar with how jackson "does things", otherwise I would have translated the "it can claim that @JsonIdentityInfo was found" to code and this issue would not be :)

There is this comment on BeanDeserializerBase

Discussions on the user-list I have found :
https://groups.google.com/d/topic/jackson-user/4jf2E_FKOIk/discussion
And the initial one between Jeff and Tatu
http://markmail.org/message/n6vrh4zi5nm3r3wt#query:+page:1+mid:nxpbwkisautcjl7u+state:results

@cowtowncoder
Copy link
Member

One way to get bit further with this would be to write a unit test that shows how things should work; and I can have a look to see what is needed to make it work.

@redben
Copy link
Author

redben commented Nov 19, 2014

@cowtowncoder
Copy link
Member

That sounds like a good start, sure.

@stickfigure
Copy link
Contributor

If there is anything specific I can do to help, let me know. The activity around this is good timing - JSOG deserialization in Java just became an issue in a project I'm working on.

@cowtowncoder
Copy link
Member

Hmmh. I take it back; amount of code needed to cut'n paste is non-trivial, so I more self-contained test would probably be needed.

I am also unlikely to have time to work on this myself, although can try to help as usual.

@cowtowncoder
Copy link
Member

Addendum to the preceding comment: while I may not have time to spearhead this, I concur that timing for getting support in for 2.5 is good now. And @stickfigure definitely knows a lot about this area.

@redben
Copy link
Author

redben commented Nov 20, 2014

I wish I could help but I don't know where to start. Should the test look more like testCustomPoolResolver or testCustomDeserializationClass ? or am I off track ? Please excuse my ignorance.

@cowtowncoder
Copy link
Member

I think it's fine to start with that code, and try to make code minimal. Could probably drop serialization code and just use hard-coded JSON (that may be generated with serializer).

@redben
Copy link
Author

redben commented Nov 20, 2014

I hope this is concise enough.

package com.jackson.jsog;

import java.io.IOException;

import org.testng.annotations.Test;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.ObjectIdGenerator;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JSOGDeserializeTest {

  /** the key of the property that holds the ref */
  public static final String REF_KEY = "@ref";

  /**
   * JSON input
   */
  private static final String EXP_EXAMPLE_JSOG = "{\"@id\":\"1\",\"foo\":66,\"next\":{\"@ref\":\"1\"}}";

  ObjectMapper mapper = new ObjectMapper();

  /**
   * Customer IdGenerator
   */
  static class JSOGGenerator extends ObjectIdGenerator<JSOGRef>  {

    private static final long serialVersionUID = 1L;
    protected transient int _nextValue;
    protected final Class<?> _scope;

    protected JSOGGenerator() { this(null, -1); }

    protected JSOGGenerator(Class<?> scope, int nextValue) {
        _scope = scope;
        _nextValue = nextValue;
    }

    @Override
    public Class<?> getScope() {
        return _scope;
    }

    @Override
    public boolean canUseFor(ObjectIdGenerator<?> gen) {
        return (gen.getClass() == getClass()) && (gen.getScope() == _scope);
    }

    @Override
    public ObjectIdGenerator<JSOGRef> forScope(Class<?> scope) {
          return (_scope == scope) ? this : new JSOGGenerator(scope, _nextValue);
    }

    @Override
    public ObjectIdGenerator<JSOGRef> newForSerialization(Object context) {
          return new JSOGGenerator(_scope, 1);
    }

    @Override
    public com.fasterxml.jackson.annotation.ObjectIdGenerator.IdKey key(Object key) {
          return new IdKey(getClass(), _scope, key);
    }

    @Override
    public JSOGRef generateId(Object forPojo) {
          int id = _nextValue;
          ++_nextValue;
          return new JSOGRef(id);
    }

  }


  /**
   * The reference deserializer
   */
  static class JSOGRefDeserializer extends JsonDeserializer<JSOGRef>
  {
    @Override
    public JSOGRef deserialize(JsonParser jp, DeserializationContext ctx) throws IOException, JsonProcessingException {
      JsonNode node = jp.readValueAsTree();
      if (node.isTextual()) {
        return new JSOGRef(node.asInt());
      } else {
        return new JSOGRef(node.get(REF_KEY).asInt());
      }
    }

  }

  /**
   * The reference object
   */
  @JsonDeserialize(using=JSOGRefDeserializer.class)
  static class JSOGRef
  {

    @JsonProperty(REF_KEY)
    public int ref;

    public JSOGRef() {
    }

    public JSOGRef(int val) {
      ref = val;
    }
  }


  /**
   * Example class using JSOGGenerator
   */
  @JsonIdentityInfo(generator=JSOGGenerator.class)
  public static class IdentifiableExampleJSOG {
    public int foo;
    public IdentifiableExampleJSOG next;
  }




  @Test
  public void testStructJSOGRef() throws Exception {

    // Because the value ({@ref:1}) is not scalar, parser thinks it is not an id 
    // and tries to deserialize as normal a new IdentifiableExampleJSOG 
    // then  complains about unrecognized field "@ref"
    IdentifiableExampleJSOG result = mapper.readValue(EXP_EXAMPLE_JSOG, IdentifiableExampleJSOG.class);

    assert 66 == result.foo;
    assert result == result.next;
  }
}

@cowtowncoder
Copy link
Member

Thanks, I think this'll work as starting point yes.

cowtowncoder added a commit that referenced this issue Nov 22, 2014
cowtowncoder added a commit that referenced this issue Dec 10, 2014
…; id class MUST implement #equals() and #hashCode()!)

Will see if polymorphic case would support, and if not, what is needed to support it.
@cowtowncoder
Copy link
Member

Ok, some good news: after adding couple things, test now actually passes. Things to note:

  • ObjectIdGenerator has 2 new methods that MUST be implemented for JSON-Object valued object ids:
    • maySerializeAsObject() simply needs to return true
    • isValidReferencePropertyName() should verify that the property name matches what is expected (in case of JSOG, seems to be hard-coded, but this need not be the case)
  • Object id instances are used as Map key, so they must override equals() and hashCode() (test didn't yet have it)
  • Support right now only works for cases where the first property is expected reference; while it would be possible to support other kinds, that would require heavier processing so I won't add it unless it is actually needed -- AFAIK, JSOG seems to use single-property objects so this should not be a problem here.

I will add another test for polymorphic type case, but I am mildly optimistic that this will be solved for 2.5.

@stickfigure
Copy link
Contributor

The jackson-jsog code has been updated with this new contract and it works great! Thanks Tatu.

Now just looking forward to the RC of 2.5!

Also, I'd happily contribute this code to jackson for inclusion in a future version.

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