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

GsonSerializer Erro include/exclude #933

Closed
jeancrbecker opened this issue Jan 29, 2015 · 56 comments
Closed

GsonSerializer Erro include/exclude #933

jeancrbecker opened this issue Jan 29, 2015 · 56 comments

Comments

@jeancrbecker
Copy link

Ao retornar um json através do result, quando este objeto retornado contém uma lista e tento excluir ou incluir algum parametro da mesma ocorre o erro abaixo:

java.lang.NullPointerException
    at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:191)
    at br.com.caelum.vraptor.serialization.Serializee.reflectField(Serializee.java:159)
    at br.com.caelum.vraptor.serialization.Serializee.getParentTypes(Serializee.java:140)
    at br.com.caelum.vraptor.serialization.Serializee.getParentTypesFor(Serializee.java:125)
    at br.com.caelum.vraptor.serialization.Serializee.excludeAll(Serializee.java:96)
    at br.com.caelum.vraptor.serialization.gson.GsonSerializer.exclude(GsonSerializer.java:59)

Um exemplo de objetos.

public class Resultado {
    private String nome;
    private Collection list;
}

public class Usuario {
    private String nome;
    private String login;
}

Então na controller quando tento retornar um json excluindo algum parametro da lista, ou tentando incluir apenas alguns parametros ocorre erro.

Isso não funciona

    @Get("search/{nome}")
    public void pesquisar(String nome) {
        result.use(json()).withoutRoot().from(abstractService.search(nome)).include("list").exclude("list.login").serialize();
    }

Isso também não

@Get("search/{nome}")
    public void pesquisar(String nome) {
        result.use(json()).withoutRoot().from(abstractService.search(nome)).include("list.nome").serialize();
    }
@lucascs
Copy link
Member

lucascs commented Jan 30, 2015

mas esse search está retornando null? Se não, vc não precisa colocar esse "list" na frente dos atributos, ele vai aplicar nos elementos da lista.

@jeancrbecker
Copy link
Author

O search funciona sem problemas, só estou com dificuldade com o include e
exclude que não funcionam para os atributos dos objetos da lista. Quando
uso .recursive() traz tudo, porem quero limitar o resultado para apenas
algumas propriedades dos objetos da lista.
Em 29/01/2015 23:16, "Lucas Cavalcanti" [email protected] escreveu:

mas esse search está retornando null? Se não, vc não precisa colocar esse
"list" na frente dos atributos, ele vai aplicar nos elementos da lista.


Reply to this email directly or view it on GitHub
#933 (comment).

@lucascs
Copy link
Member

lucascs commented Jan 30, 2015

Tentou tirar o "list."?

@jeancrbecker
Copy link
Author

Sim também da erro, veja que no .from() tenho um objeto, além de alguns
parâmetros ele também tem uma lista, então desta lista é que preciso enviar
apenas alguns parâmetros e não todos, por isso estou tentando usar o
include e o exclude mas sem sucesso.

Em 30/01/2015 01:27, "Lucas Cavalcanti" [email protected] escreveu:

Tentou tirar o "list."?


Reply to this email directly or view it on GitHub
#933 (comment).

@jeancrbecker
Copy link
Author

Lucas, vi que tem outra issue aberta na versão 3 do VRaptor, o problema é o mesmo que estou enfrentando. caelum/vraptor#438

@Turini Turini added the bug label Feb 2, 2015
@jeancrbecker
Copy link
Author

Tem algum paliativo que eu possa usar @Turini @lucascs ?

@Turini
Copy link
Member

Turini commented Feb 9, 2015

Opa @jeancrbecker, dei uma olhada agora. Com o mesmo cenário que você,
fiz um include em "list" e depois um exclude em "list.nome" e funcionou sem
nenhum problema. Não consegui reproduzir seu bug. Você consegue escrever
um projeto simples reproduzindo o bug e subir no github ou dropbox da vida?

@Turini
Copy link
Member

Turini commented Feb 9, 2015

Ahhh, esqueci de perguntar. Qual a versão do VRaptor que você está usando?

@jeancrbecker
Copy link
Author

@Turini mas você serializou a partir de um objeto "container" ? Veja que quando eu serializo uma lista no .from() funciona sem problemas o acesso à suas propriedades, acontece o erro quando eu tenho um objeto, digamos pessoa, e esse objeto pessoa tem uma lista de telefones por exemplo, quando tento fazer
List pessoas = servico.consulta();
...from(pessoas).include("telefones.tipoTelefone")... então não funciona.
Estou usando a versão 4.1.0

@Turini
Copy link
Member

Turini commented Feb 9, 2015

eu escrevi o código que você postou aqui na issue:

result.use(json()).withoutRoot()
    .from(abstractService.search(nome))
    .include("list").exclude("list.login").serialize();

com as mesmas classes que você postou aqui tb

@jeancrbecker
Copy link
Author

Pode me enviar ou me passar um link de um projeto de testes padrão vraptor ? Ai posso usar como base para escrever o teste com meu problema. Acho que fica mais fácil para eu te passar.

@Turini
Copy link
Member

Turini commented Feb 9, 2015

essa sua Collection<Usuario> list vem do hibernate? se sim, será que é por
ser um proxy que ainda não foi inicializado? tenta chamar um getList().size()
antes de mandar serializar pra testar?

@Turini
Copy link
Member

Turini commented Feb 9, 2015

o link do projeto de testes que pediu:
https://github.com/caelum/vraptor4/tree/master/vraptor-blank-project
fica nesse mesmo repositório

@jeancrbecker
Copy link
Author

@Turini
Copy link
Member

Turini commented Feb 10, 2015

oi @jeancrbecker, baixei aqui o projeto e o problema é que você faz:

.include("telefones").include("telefones.tipo")

No primeiro include, do "telefones", ele já poe o "tipo". Tirando o segundo
include o resultado vai ser o json com a lista de telefones e todos seus
atributos nele. Se você mudar o teste, colocando exclude "telefone.tipo":

.include("telefones").exclude("telefones.tipo")

Vai ver que ele funciona tb. adiciona todos os campos do telefone, menos o tipo.

@jeancrbecker
Copy link
Author

@Turini estranho pq aqui se eu faço

result.use(json()).withoutRoot().from(popularPessoa()).include("telefones").serialize(); 

O teste da erro e o json resultado não contém o "tipo"

Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.189 sec <<< FAILURE!
shouldNotThrowNullPointersOnJsonResultList(br.com.jbkr.jbkr.test.JsonResultTest)  Time elapsed: 0.139 sec  <<< FAILURE!
java.lang.AssertionError: 
Expected: is "[{\"nome\":\"João\",\"telefones\":[{\"numero\":\"9999-1111\",\"tipo\":{\"nome\":\"1\"}},{\"numero\":\"9999-2222\",\"tipo\":{\"nome\":\"2\"}}]},{\"nome\":\"Maria\",\"telefones\":[{\"numero\":\"9999-1111\",\"tipo\":{\"nome\":\"1\"}},{\"numero\":\"9999-2222\",\"tipo\":{\"nome\":\"2\"}}]},{\"nome\":\"Fulano\",\"telefones\":[{\"numero\":\"9999-1111\",\"tipo\":{\"nome\":\"1\"}},{\"numero\":\"9999-2222\",\"tipo\":{\"nome\":\"2\"}}]}]"
     but: was "[{\"nome\":\"João\",\"telefones\":[{\"numero\":\"9999-1111\"},{\"numero\":\"9999-2222\"}]},{\"nome\":\"Maria\",\"telefones\":[{\"numero\":\"9999-1111\"},{\"numero\":\"9999-2222\"}]},{\"nome\":\"Fulano\",\"telefones\":[{\"numero\":\"9999-1111\"},{\"numero\":\"9999-2222\"}]}]"

@Turini
Copy link
Member

Turini commented Feb 10, 2015

ah, entendi! É que faltou o .recursive() no seu exemplo. Muda pra:

result.use(json()).withoutRoot().from(popularPessoa())
    .include("telefones").recursive().serialize();

É que o tipo é uma outra classe, por padrão só o include não rola mesmo.

@jeancrbecker
Copy link
Author

@Turini Esse é o problema eu não posso usar o recursive(), esse é só um exemplo básico, mas no meu projeto o objeto tem vários níveis, então vai acabar caindo em referencia circular ou mesmo onerando o tempo devido ao excesso de dados desnecessários.
Por isso eu tento usar o include().

@linyatis
Copy link

@Turini, pelo que entendi do caso do @jeancrbecker, acho que ele pode fazer:
.include("telefones", "telefones.tipo")

Assim como o @jeancrbecker, eu costumo não usar o recursive() por conta de referências circulares e objetos lazy.
Eu uso o include aqui com vários níveis e funciona normalmente.

@jeancrbecker
Copy link
Author

Quando tento fazer include do "tipo" ele da o erro abaixo:

Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.118 sec <<< FAILURE!
shouldNotThrowNullPointersOnJsonResultList(br.com.jbkr.jbkr.test.JsonResultTest)  Time elapsed: 0.073 sec  <<< ERROR!
java.lang.IllegalArgumentException: Field path 'telefones.tipo' doesn't exists in interface java.util.List
    at br.com.caelum.vraptor.serialization.Serializee.getParentTypes(Serializee.java:145)
    at br.com.caelum.vraptor.serialization.Serializee.getParentTypesFor(Serializee.java:129)
    at br.com.caelum.vraptor.serialization.Serializee.includeAll(Serializee.java:118)
    at br.com.caelum.vraptor.serialization.gson.GsonSerializer.include(GsonSerializer.java:126)
    at br.com.jbkr.jbkr.test.JsonResultTest.shouldNotThrowNullPointersOnJsonResultList(JsonResultTest.java:42)

@linyatis
Copy link

Sua lista não está com o tipo de objeto definido:

public List getTelefones() {
            return telefones;
        }

Tenta com o abaixo que deve funcionar:

public List<Telefone> getTelefones() {
            return telefones;
        }

@linyatis
Copy link

Ah, muda também o seu atributo dentro da classe pessoa:

public class Pessoa {

        List<Telefone> telefones;

@jeancrbecker
Copy link
Author

Ai que está o problema, não posso definir tipo. Eu estou fazendo um módulo genérico pra evitar o DRY, ja consegui desenvolver toda a parte de controller e serviços genéricos para CRUD, agora o objeto que vai carregar as listas repassar para o result serializar via JSON para minha app Angular não sabe qual é a tipagem da lista. Vi que quando tem a tipagem realmente eu consigo fazer o include do "telefones.tipo", será que tem alguma ideia de como posso ter o mesmo comportamento sem a tipagem ?

@linyatis
Copy link

Entendi, @jeancrbecker. Na verdade eu também já tive esse problema que você está comentando.
Pelo que vi o include() não suporta objetos genéricos, né @Turini?

Mas também acho que seria uma feature interessante permitir objetos genéricos.

@jeancrbecker
Copy link
Author

Pois é, senão vou ter que criar um container diferente para cada tipo de objeto que vier do banco para mandar pra tela, ai o DRY vai comer solto rsrs.

@Turini
Copy link
Member

Turini commented Feb 11, 2015

É sim. Agora entendi o problema. @jeancrbecker, por favor corrige a descrição da
issue que diz que o .include("list").exclude("list.login")não funciona. É o
include em tipo generico que não funciona, né? Ou o exclude não funciona pra você?

@jeancrbecker
Copy link
Author

@Turini O exclude quando é lista generica também nao funciona.

@Turini
Copy link
Member

Turini commented Feb 11, 2015

quando você diz genérica, quer dizer sem o generics então, né? :) tipo List telefones

@Turini
Copy link
Member

Turini commented Feb 11, 2015

ou você está fazendo com List<T> ou algo assim?

@jeancrbecker
Copy link
Author

@Turini Pior que não consigo senão vou ter que escrever um container para cada objeto, mas aí estaria fazendo o Ctrl+c Ctrl+v o que não acho muito legal, esse list teria que ser mesmo genérico.

@asouza
Copy link
Contributor

asouza commented Feb 19, 2015

Dá para usar uma annotation que nem o Hibernate usava antes, para suportar
mapeamento de coleções que não usavam Generics.

@onetomany(targetEntity = Telefone.class)

Claro que devemos criar outra annotation... Acho até uma feature justa :).

Em Thu Feb 19 2015 at 11:43:54 AM, jeancrbecker [email protected]
escreveu:

@Turini https://github.com/Turini Pior que não consigo senão vou ter
que escrever um container para cada objeto, mas aí estaria fazendo o Ctrl+c
Ctrl+v o que não acho muito legal, esse list teria que ser mesmo genérico.


Reply to this email directly or view it on GitHub
#933 (comment).

@Turini Turini modified the milestone: 4.2.0.Final Feb 20, 2015
@rafaelGuerreiro
Copy link

[Off-topic]
Eu acredito que essa issue evidencia um problema de design de código. O que aconteceria caso a sua entidade Resultado sofresse uma alteração em que fosse incluído mais um atributo, que poderia ser uma lista?

Eu evito serializar as entidades, pois, dependendo da alteração, ela pode quebrar os meus JSONs. E, provavelmente, você vai ter que ficar caçando aonde você deve fazer include e aonde você deve fazer exclude.

Por isso eu crio wrappers específicos para cada tipo de JSON que eu pretendo serializar. Essa classe vai ser usada apenas para ser serializada:

@Entity
public class Company {
   private Long id;
   private String name;
   private String address;

   private List<Customer> customers;

   private List<BankAccount> accounts;
}

Se eu quiser serializar a Company para retornar apenas o id, nome e address para uma determinada view, eu crio o seguinte:

public class SomeViewCompanyWrapper {
   private final Long id;
   private final String name;
   private final String address;

   public SomeViewCompanyWrapper(Company c) {
      this.id = c.getId();
      this.name = c.getName();
      this.address = c.getAddress();
   }
}

Assim, posso serializar o objeto e, só vou alterá-lo quando eu precisar alterar a minha view.

No caso da lista, poderia ficar assim:

public class OtherViewCompanyWrapper {
   private final Long id;
   private final String name;
   private final List<OtherViewCustomerWrapper> customers;

   public SomeViewCompanyWrapper(Company c) {
      this.id = c.getId();
      this.name = c.getName();

      this.customers = toWrapper(c.getCustomers());
   }

   private List<OtherViewCustomerWrapper> toWrapper(List<Customer> customers) {
      // Implementação.
      return new ArrayList<>();
   }
}

Assim, posso usar o recursive() sem medo de que tem coisa a mais ou de que minha performance vai ser prejudicada.

@jeancrbecker
Copy link
Author

@rafaelGuerreiro Esse é exatamente o caso, esse é um Wrapper especifico para um JSON meu que monta tabelas, ele é genérico justamente porque é exatamente igual para todos meus objetos básicos, muda apenas o conteúdo da lista, por isso preciso dos includes e excludes que tenho parametrizados e bem desacoplados em cada controller, sem essa correção da issue vou ter que criar o mesmo objeto para cada entidade minha do banco, imagine só que bonito o código. ResultadoPessoa.class, ResultadoEndereco.class, ResultadoBairro.class, ResultadoEtc.class... todos com os mesmos campos apenas sendo diferenciados pela tipagem da lista.

@rafaelGuerreiro
Copy link

Mas você não está colocando a entidade na lista? Não seria ideal fazer um wrapper genérico para essa lista? Algo assim:

private final List<ResultadoWrapperOfList> list;
public class ResultadoWrapperOfList {
   // Atributos das entidades que devem ser usados da mesma forma

   public ResultadoWrapperOfList (Object o) {
      // Carrega os atributos. pode usar algum framework de mapeamento
   }
}

Logo, você vai poder usar o recursive()...

@jeancrbecker
Copy link
Author

@rafaelGuerreiro Não pois a responsabilidade de montar a tabela é da view(AngularJS), de acordo com a view ela sabe quais campos estão vindo e se ocupa de montar a tabela. Uma view pode ter a tabela com o campo Nome e outra não, então cairia no mesmo problema, teria que ter um wrapper para cada tipo de objeto que fosse serializado na lista o que não é ideal. Já tenho a arquitetura funcionando sem problemas, só esbarro no problema de que por não funcionar o include/exclude dos campos da lista quando preciso mandar um campo de relacionamento ele não vai para view, exemplo: está indo todos os dados para montar a tabala de bairro, mas não tenho na view o relacionamento com o campo Cidade para exibir na tabela.

@rafaelGuerreiro
Copy link

Como o @Turini disse, tente usar algum type na lista.

Você pode usar algo assim:

public class Resultado<T> {
    private List<T> lista;
}

Assim, você pode setar o tipo da lista na hora de instanciar o Resultado... Vai ficar meio esquisito:

Resultado<Customer> r = new Resultado<Customer>(company); // Usando as minhas classes mencionadas acima...

Talvez você consiga resolver se fizer isso:

public class Resultado {
    private List<? extends Object> lista;
}

Você já tentou fazer isso?

@rafaelGuerreiro
Copy link

@asouza Acho que essa anotação não resolveria o problema do @jeancrbecker, pois ele não tem como saber qual será o tipo da Collection. Como não conseguimos mudar o valor do parâmetro da annotation em runtime, acredito que a saída mais plausível seria usar o Generic Type mesmo...

Acho que essa anotação serveria para manter compatibilidade com códigos legados em que não fizeram o uso do Generic Type, é essa a sua ideia?

@jeancrbecker
Copy link
Author

@rafaelGuerreiro tentei usar com "? extends Object" mas também não funcionou, só funciona quando coloco explicitamente List por exemplo. Com Generic Type também não rolou.

@nhada
Copy link

nhada commented Mar 23, 2015

Pessoal,
Tudo indica que estou aqui com o mesmo problema! Já existe uma solução?

@jeancrbecker
Copy link
Author

Pois é @nhada o projeto do VRaptor parece estar meio abandonado, não tive retorno também.

@felipeweb
Copy link
Contributor

@jeancrbecker e @nhada estamos trabalhando nisso e traremos uma solução o mais rápido o possível.

@Turini
Copy link
Member

Turini commented Apr 1, 2015

@jeancrbecker, perdão pela demora. Tirei a label de bug, porque esse é o comportamento esperado mesmo, já que a informação genérica é essencial pro include/exclude do serializador funcionar nesses casos. Pra ajudar com o seu caso de uso, mandei um pull request #961 aumentando a visibilidade de um método da classe Serializee e passando o field como parametro, para que você possa fazer algo como:

@Specializes 
public class CustomSerializee extends Serializee {
@Override
protected static Class<?> getActualType(Field field) {
        if (field.isAnnotationPresent(GenericType.class)) {
            return field.getAnnotation(GenericType.class).value();
        }
        return super.getActualType(field);
    }
}

Isso vai funcionar de forma parecida com a estratégia do hibernate, você vai criar uma annotation -- que nesse caso eu chamei de GenericType -- parecida com:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GenericType {
    Class<?> value();
}

E anotar seus atributos dessa forma:

public class Pessoa {

        @GenericType(Telefone.class) 
        private List telefones;
}

Faz sentido? Fiz essa alteração no código de testes que você me enviou e funciona perfeitamente. Essa alteração pode entrar no próximo release, que não deve demorar pra sair (se tudo correr bem, semana que vem). Se quiser, até lá posso gerar um snapshot pra você ir usando/testando. É só me avisar

@Turini Turini self-assigned this Apr 1, 2015
@jeancrbecker
Copy link
Author

Me parece perfeito Turini, se puder gerar um snap posso ir alterando meu
projeto. Desde já agradeço.
Em 01/04/2015 00:42, "Rodrigo Turini" [email protected] escreveu:

@jeancrbecker https://github.com/jeancrbecker, perdão pela demora.
Tirei a label de bug, porque esse é o comportamento esperado mesmo, já que
a informação genérica é essencial pro include/exclude do serializador
funcionar nesses casos. Pra ajudar com o seu caso de uso, mandei um pull
request #961 #961 aumentando a
visibilidade de um método da classe Serializee e passando o field como
parametro, para que você possa fazer algo como:

@specializes public class CustomSerializee extends Serializee {@Overrideprotected static Class<?> getActualType(Field field) {
if (field.isAnnotationPresent(GenericType.class)) {
return field.getAnnotation(GenericType.class);
}
return super.getActualType(field);
}
}

Isso vai funcionar de forma parecida com a estratégia do hibernate, você
vai criar uma annotation -- que nesse caso eu chamei de GenericType --
parecida com:

@target(ElementType.FIELD)
@retention(RetentionPolicy.RUNTIME)public @interface GenericType {
Class<?> value();
}

E anotar seus atributos dessa forma:

public class Pessoa {

    @GenericType(Telefone.class)
    private List telefones;

}

Faz sentido? Fiz essa alteração no código de testes que você me enviou e
funciona perfeitamente. Essa alteração pode entrar no próximo release, que
não deve demorar pra sair (se tudo correr bem, semana que vem). Se quiser,
até lá posso gerar um snapshot pra você ir usando/testando. É só me avisar


Reply to this email directly or view it on GitHub
#933 (comment).

@Turini
Copy link
Member

Turini commented Apr 1, 2015

Perfeito! Acabei de deployar o 4.2.0-RC2-SNAPSHOT. Me avisa se deu certo?

@jeancrbecker
Copy link
Author

@Turini estou tentando fazer a classe especializada mas não é possível fazer o Override do método, não sei se estou esquecendo alguma coisa mas simplesmente não funciona.

@Turini
Copy link
Member

Turini commented Apr 2, 2015

putz, acabei de ver que ele é estático (sabe se lá o pq).
peraí que vou alterar e atualizar o snapshot... já te aviso

@Turini
Copy link
Member

Turini commented Apr 2, 2015

pronto, você atualiza e testa de novo? a versão é a mesma

@jeancrbecker
Copy link
Author

Vou testar

Att.

Jean C. Becker
(41) 9921-8625 - [email protected]
JBKR - Tecnologia e Inteligência com foco no desenvolvimento de conteúdos
para WEB
Acesse : www.jbkr.com.br
"The two most important days in your life are the day you are born and the
day you find out why"

Em 2 de abril de 2015 01:19, Rodrigo Turini [email protected]
escreveu:

pronto, você atualiza e testa de novo? a versão é a mesma


Reply to this email directly or view it on GitHub
#933 (comment).

@Turini
Copy link
Member

Turini commented Apr 6, 2015

@jeancrbecker, me avisa quando testar, ta bem?
assim podemos fazer um release com essa e outras alterações :)

@jeancrbecker
Copy link
Author

@Turini funcionou perfeitamente! Obrigado pelo auxilio.

@Turini
Copy link
Member

Turini commented Apr 7, 2015

excelente! logo teremos um novo release com essa alteração
(tudo correndo bem ele deve acontecer ainda nessa semana)

@Turini Turini closed this as completed Apr 7, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants