2

I am trying to convert a simple array of string elements in a json document to a java List, which is inside a bean using genson. I was expecting this to happen automatically, but it is not working. Am I missing something?

Here is my Json-String:

{"id":12345,"permissions":["READ_XX","WRITE_XX"]}

And here is the output of my object, showing that my permissions-list is not bein populated:

DefaultRole [id=12345, name=null, permissions=[]]

My DefaultRole class looks like this:

public class DefaultRole implements Role
{
  public Id id = null;

  @XmlElementWrapper(name = "permissions")
  @XmlElement(name = "permission")
  private List<String> permissions = Lists.newArrayList();

  @Inject
  public DefaultRole()
  {

  }
  [...]

The JAXB annotations are only for XML-conversion. They shouldn't play a role here.

I am creating the Genson object like this:

Genson genson = new Genson.Builder()
  .setWithClassMetadata(false)
  .setWithBeanViewConverter(true)
  .setWithDebugInfoPropertyNameResolver(true)
  .setSkipNull(true)
  .create();

My deserialize call:

genson().deserialize(jsonString, DefaultRole.class)

Does anyone have a tip on how I can deseriealize my json string array into a java List object residing inside a bean?

EDIT [SOLVED PARTLY - STILL ONE QUESTION OPEN]

I have got a step further now. I went back to the roots and started off with a basic bean that simply has a list of strings. And as expected the first time, this works. The difference between the previous object is mainly that this one follows the bean specification. The other object (DefaultRole) follows the fluid-object-pattern (not sure if that is the correct denfinition). Basically, instead of the setter method being void I return the object, so that the next value can easily be set in a fluid manner.

So this works:

public void setPermissions(List<String> permissions)
{
  this.permissions = permissions;
}

This does not work:

public Role setPermissions(List<String> permissions)
{
  this.permissions = permissions;
  return this;
}

Has anyone else run into this and what are the alternatives to switching all my beans to adhere to the bean-specification? Is the only option to use pure field-level population instead of via setter method?

EDIT [ALMOST SOLVED]

Hi, I am not sure how it is best to answer questions where code is needed to help understand what I did.

Anyway, thanks very much for your help. I really like the third option, which is the type of thing that I was looking for. Unfortunately I am now getting the error "No constructor has been found for type class xxx.DefaultRole". According to what you said in your answer this should not happen when returning Trilean.UNKNOWN as the search then continues.

I am adding the following code to my genson-builder:

        .set(new BeanMutatorAccessorResolver.BaseResolver()
        {
            @Override
            public Trilean isMutator(Method method, Class<?> fromClass)
            {
                if (Reflect.isSetter(method))
                    return Trilean.TRUE;

                else
                    return Trilean.UNKNOWN;
            }
        })

My Reflect.isSetter(method) looks like this (code adapted from here: http://www.asgteach.com/blog/?p=559):

public static boolean isSetter(Method method)
{
    return Modifier.isPublic(method.getModifiers()) &&
        (method.getReturnType().equals(void.class) || method.getReturnType().equals(method.getDeclaringClass())) &&
        method.getParameterTypes().length == 1 &&
        method.getName().matches("^set[A-Z].*");
}

The BaseResolver returns Trilean.UNKNOWN for everything that has not been implemented. Therefore it should find the constructor using the standard logic, should it not?

EDIT [SOLVED]

Just for completeness, I'll post the code that actually works:

public static final Genson genson()
{
    Genson genson = new Genson.Builder()
        .setSkipNull(true)
        .setWithClassMetadata(false)
        .setWithDebugInfoPropertyNameResolver(true)
        .setWithBeanViewConverter(true)
        .with(new BeanMutatorAccessorResolver.BaseResolver()
        {
            @Override
            public Trilean isMutator(Method method, Class<?> fromClass)
            {
                if (Reflect.isSetter(method))
                    return Trilean.TRUE;

                else
                    return Trilean.UNKNOWN;
            }
        })
        .create();

    return genson;
}

It is important to note here that the ".set(new BeanMutatorAccessorResolver.BaseResolver()" had to be replaced with ".with(new BeanMutatorAccessorResolver.BaseResolver()" (notice "with" instead of "set"). This is important as the standard Resolvers are no longer used otherwise and you'll end up with the error that I had, where the constructor could no longer be found.

The isSetter method looks like this:

public static boolean isSetter(Method method)
{
    return Modifier.isPublic(method.getModifiers())
        && (method.getReturnType().equals(void.class) || method.getReturnType().isAssignableFrom(method.getDeclaringClass()))
        && method.getParameterTypes().length == 1
        && method.getName().matches("^set[A-Z].*");
}

Here it is important to note that I had originally used "equals" instead of "isAssignableFrom" when comparing the return-type to the declaring-class. This only works when the return-type is exactly the same class as the one it is declared in. When using the interface as the return-value however it no longer works. By using "method.getReturnType().isAssignableFrom(method.getDeclaringClass())" instead this also works for interfaces (including super interfaces).

Thanks, Michael

michaeldd
  • 514
  • 2
  • 6
  • 18
  • indeed it should, what version are you using? (BTW you should add a comment or something so people get notified that you have another question etc) – eugen Feb 23 '14 at 14:41
  • you can also post a message on gensons mailing list with a minimal example reproducing your problem – eugen Feb 23 '14 at 14:54
  • Thanks, I am using 0.98. – michaeldd Feb 23 '14 at 14:57
  • Ok, will do next time, thanks. – michaeldd Feb 23 '14 at 14:58
  • Hum I am not able to reproduce your problem. This should work. Can you please post a question on the ML with a complete example to reproduce this behaviour (a main with genson setup + DefaultRole definition, so I only have to run it to see what happens)? – eugen Feb 23 '14 at 15:01
  • Ok, will do, thanks. I haven't actually changed anything else yet - only added the new BeanMutatorAccessorResolver. As soon as I remove that it seems to work again (with void methods). I have debugged to see whether it goes into the correct if-block and that part seems to be ok. – michaeldd Feb 23 '14 at 15:18

1 Answers1

1

Indeed Genson (as most other libs) use Java beans conventions to detect a setter/getter, thus it won't detect your setPermissions as a setter.

Two quick solutions:

1) You can annotate it with @JsonProperty (no need to define the name). This will tell genson to use it as a setter (however genson will not reuse the returned object, meaning that in your case it works, but builder like is nut supported like that).

2) You can force genson to use only fields and no methods (this directly sets the field value without calling get/set).

Genson.Builder()
        .setUseFields(true)
        .setFieldFilter(VisibilityFilter.DEFAULT)
        .setUseGettersAndSetters(false)
      .create();

More generic:

3) add a custom BeanMutatorAccessorResolver in the chain, that will handle your specific case

Genson genson = Genson.Builder().with(new BeanMutatorAccessorResolver.BaseResolver() {
        @Override
        public Trilean isMutator(Method method, Class<?> fromClass) {
            // if method starts with setXXX and return type same as method.getDeclaringClass
            return Trilean.TRUE;
            // else return Tilean.UNKNOWN genson will use its built-in resolvers
        }
    }).create();

EDIT

Should have seen that you were using set instead of with method on the builder. When you see setXXX in genson API this usually means that you will override/force some mechanism (you set a value), but with is used to add behaviour.

eugen
  • 5,856
  • 2
  • 29
  • 26
  • BTW since version 0.95 genson has [partial support for JAXB annotations](http://code.google.com/p/genson/wiki/JaxbBundle), so you could maybe even use to have similar behaviour between json & xml ser/deser, just make some tests before to ensure it is symetric – eugen Feb 23 '14 at 11:41
  • Hi, I have answered your question in my initial question. I have seen that there is JAXB support in genson, but I am trying to go the genson way as far as possible (and use as little JAXB annotations as possible). I am using this in combination with jersey and my preferred way is to get jersey working nicely with genson, rather than genson with JAXB. I am really happpy with genson - there are just one or two things I need to understand to make it perfect. – michaeldd Feb 23 '14 at 14:55
  • YES, you are absolutely right and I saw the "with" method and wondered ... sometimes it is best just to try out. The "setXX" did sound like as if I am setting (and overriding) the original value with my one. Anyway it works now, thanks very much for your time! – michaeldd Feb 23 '14 at 17:28