Question

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

Était-ce utile?

La solution

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top