Vraptor + Angular + JSON

26 respostas
L

Pessoal,

Estou fazendo uma aplicação com Vraptor 3.5.0 + Angular e em determinado momento preciso enviar um JSON para o controller. Até aí tudo bem quando o JSON é simples o Vraptor consegue entender normalmente, porém quando o JSON é mais complexo com listas dentro de listas, o objeto das listas (listPeriod) sempre chega nulo ao controller, segue exemplo:

Com esse objeto o Vraptor popula normalmente

var teste = angular.toJson({request : 	{clientCode: 'xxx'})

Com um objeto desse tipo já não rola:

var teste = angular.toJson({
						request : 	{
								clientCode: 'XXXX',
								listPeriod: [{
									             	tariff : 	{
							             					from: '10/09/2013', 
												        to: '15/09/2013'
													} 
										}]
								}
						});

Na parte Java o código está assim:

@Path("/mockSearch")
@Consumes("application/json")
public void teste(HotelPriceRequest request) throws IOException {
      //IMPLEMENTACAO
}

Alguma idéia?

Muito Obrigado desde já…

26 Respostas

Lucas_Cavalcanti

os nomes dos campos estão corretos?

vc está usando o XStream ou o GSon?

L

Oi Lucas,

Sim os nomes estão certinhos, inclusive chega o primeiro parâmetro certo request.clientCode e além disso se eu passar esses campos como queryString chega certinho no controller.

Eu estou com as libs do XStream e do Gson no projeto, porém não sei qual é o padrão do Vraptor para desserializar JSON…

Pelo que andei lendo parece que o Gson é o mais indicado para esse tipo de caso, é isso mesmo? Se for isso, como fazer para que o VRaptor use o GSON? Preciso implementar algo ?

Abs e desde já muito obrigado pela ajuda…

Lucas_Cavalcanti

para usar o GSON vc precisa colocar pacotes no web.xml…

br.com.caelum.vraptor.serialization.gson e br.com.caelum.vraptor.deserialization.gson

(no parametro de packages do VRaptor)

tenta usá-los e ver se funciona.

L

Oi Lucas,

Resolvi o problema…

Atualizei para a lib mais nova do VRaptor 3.5.2 (Snapshot) e registrei o gson no web.xml. Ao fazer isso as listas chegaram preenchidas corretamente no controller…

Muito Obrigado pela ajudaaaaa
Lucas

L

Vi que a troca da versão e o registro no web.xml dos packages gerou um efeito colateral indesejado…

A deserialização do json passou a funcionar normalmente através do GSON, porém quando vou serializar, tenho tomado o seguinte erro

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘gsonDeserialization’: Unsatisfied dependency expressed through constructor argument with index 1 of type [br.com.caelum.vraptor.deserialization.gson.JsonDeserializers]: : Error creating bean with name ‘defaultJsonDeserializers’: Unsatisfied dependency expressed through constructor argument with index 0 of type [java.util.List]: : No matching bean of type [com.google.gson.JsonDeserializer] found for dependency [collection of com.google.gson.JsonDeserializer]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.google.gson.JsonDeserializer] found for dependency [collection of com.google.gson.JsonDeserializer]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘defaultJsonDeserializers’: Unsatisfied dependency expressed through constructor argument with index 0 of type [java.util.List]: : No matching bean of type [com.google.gson.JsonDeserializer] found for dependency [collection of com.google.gson.JsonDeserializer]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.google.gson.JsonDeserializer] found for dependency [collection of com.google.gson.JsonDeserializer]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

O interessante é que antes dessas mudanças o que acontecia era exatamente o contrário, ou seja, conseguia serializar mas não conseguia deserializar

<context-param>  
	    <param-name>br.com.caelum.vraptor.packages</param-name>  
	    <param-value>br.com.caelum.vraptor.util.jpa,  
	       br.com.caelum.vraptor.serialization.gson,  
	       br.com.caelum.vraptor.deserialization.gson  
	    </param-value>  
	</context-param>

Alguma ideia?

Muito Obrigado,
Lucas

Lucas_Cavalcanti

pra isso é preciso usar o VRaptor 3.5.2-SNAPSHOT…

a versao final do 3.5.2 vai sair em breve

https://oss.sonatype.org/content/repositories/snapshots/br/com/caelum/vraptor/3.5.2-SNAPSHOT/

L

Oi Lucas,

Eu já estava usando essa versão 3.5.2 com a versão de 14 de agosto e o problema ainda acontece…

Tem mais alguma ideia?

Agradeço bastante…

Abs,
Lucas

Lucas_Cavalcanti

Muito estranho… ele tá reclamando da falta de uma dependência que existe:

será que vc não está com mais de um jar do VRaptor no classpath?

já tentou dar um clean no projeto e no servidor?

Lucas_Cavalcanti

=)

L

Oi Lucas,

Só estou com essa versão no classpath: vraptor-3.5.2-20130814.144000-8

E não tinha feito o clean no projeto e no server, mas já fiz e infelizmente não resolveu o problema não…

É esse o erro:

br.com.caelum.vraptor.InterceptionException: exception raised, check root cause for details: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘gsonJSONSerialization’: Unsatisfied dependency expressed through constructor argument with index 3 of type [br.com.caelum.vraptor.serialization.gson.VRaptorGsonBuilder]: : Error creating bean with name ‘VRaptorGsonBuilder’: Unsatisfied dependency expressed through constructor argument with index 1 of type [br.com.caelum.vraptor.serialization.xstream.Serializee]: : No matching bean of type [br.com.caelum.vraptor.serialization.xstream.Serializee] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [br.com.caelum.vraptor.serialization.xstream.Serializee] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘VRaptorGsonBuilder’: Unsatisfied dependency expressed through constructor argument with index 1 of type [br.com.caelum.vraptor.serialization.xstream.Serializee]: : No matching bean of type [br.com.caelum.vraptor.serialization.xstream.Serializee] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [br.com.caelum.vraptor.serialization.xstream.Serializee] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

Abs,
Lucas

Lucas_Cavalcanti

Gerei outro snapshot, tenta lá de novo, por favor:

https://oss.sonatype.org/content/repositories/snapshots/br/com/caelum/vraptor/3.5.2-SNAPSHOT/vraptor-3.5.2-20130815.171520-9.jar

L

Oi Lucas,

Testei aqui e funcionou perfeitamente…

Percebi um outro bug que acho que é do GSON… Um dos meus parâmetros se chama “from” e o outro “to”, parâmetros com esses nomes, a deserialização usando o GSON gera o erro abaixo

br.com.caelum.vraptor.view.ResultException: Unable to deserialize data

at br.com.caelum.vraptor.deserialization.gson.GsonDeserialization.deserialize(GsonDeserialization.java:97)

at br.com.caelum.vraptor.interceptor.DeserializingInterceptor.intercept(DeserializingInterceptor.java:87)

at br.com.caelum.vraptor.core.LazyInterceptorHandler.execute(LazyInterceptorHandler.java:59)

Trocando simplesmente o nome do parâmetro, o objeto chega certinho no controller… É isso mesmo, esses nomes são reservados? Ou devo fazer alterar alguma coisa…

Abs,
Lucas

Lucas_Cavalcanti

estranho… tem algum caused by da stacktrace que explica melhor o erro?

L

Oi Lucas,

Caused by não tem… segue ela completa…

br.com.caelum.vraptor.view.ResultException: Unable to deserialize data
	at br.com.caelum.vraptor.deserialization.gson.GsonDeserialization.deserialize(GsonDeserialization.java:97)
	at br.com.caelum.vraptor.interceptor.DeserializingInterceptor.intercept(DeserializingInterceptor.java:87)
	at br.com.caelum.vraptor.core.LazyInterceptorHandler.execute(LazyInterceptorHandler.java:59)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
	at br.com.caelum.vraptor.interceptor.InstantiateInterceptor.intercept(InstantiateInterceptor.java:48)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
	at br.com.caelum.vraptor.interceptor.ExceptionHandlerInterceptor.intercept(ExceptionHandlerInterceptor.java:67)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
	at br.com.caelum.vraptor.interceptor.ParametersInstantiatorInterceptor.intercept(ParametersInstantiatorInterceptor.java:96)
	at br.com.caelum.vraptor.core.LazyInterceptorHandler.execute(LazyInterceptorHandler.java:59)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
	at br.com.caelum.vraptor.interceptor.FlashInterceptor.intercept(FlashInterceptor.java:83)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
	at br.com.caelum.vraptor.interceptor.ResourceLookupInterceptor.intercept(ResourceLookupInterceptor.java:69)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
	at br.com.caelum.vraptor.core.EnhancedRequestExecution.execute(EnhancedRequestExecution.java:44)
	at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:91)
	at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:58)
	at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:88)
	at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1212)
	at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:399)
	at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
	at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
	at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:766)
	at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:450)
	at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
	at org.mortbay.jetty.Server.handle(Server.java:326)
	at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
	at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:945)
	at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
	at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
	at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
	at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:410)
	at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)

Vou ver se consigo alguma coisa para escapar (esses parâmetros com esses nomes) ou no pior caso vou mudar o nome do parâmetro…

Abs,
Lucas

L

Oi Lucas,

Na verdade vi aqui que o problema é que esses 2 campos são do tipo Date e por serem Date o Vraptor com o Gson não estão conseguindo interpretar…

O formato que estou enviando é dd/MM/yyyy. Tenho até um Converter implementado mas quando chega no Converter o value está nulo, ou seja, me parece que primeiramente o Vraptor passa pela deserialização do Gson que não entende a data e coloca null no valor e o meu converter nesse caso não funciona de nada…

Alguma idéia?

Abs,
Lucas

Lucas_Cavalcanti

o Converter não vai funcionar para o JSON… vc precisa escrever um JsonDeserializer de date, que usa o seu formato de data:

@Component
public class DateConverter implements JsonDeserializer<Date> { .... }
L

Valeu Lucas… Funcionou legal !!!

henriquemeira

Olá Lucas, desculpe reabrir este seu post, mas é que estou enfrentando o mesmo problema que você e não consigo encontrar uma solução adequada.

Gostaria que pudesse me explicar melhor sobre dois aspectos em seu código de exemplo:

O primeiro, é sobre o teu JSON montado via angular:

var teste = angular.toJson({  
                        request :   {  
                                clientCode: 'XXXX',  
                                listPeriod: [{  
                                                    tariff :    {  
                                                            from: '10/09/2013',   
                                                        to: '15/09/2013'  
                                                    }   
                                        }]  
                                }  
                        });

Eu utilizo algo similar, mas faço o trabalho de transmissão via jQuery, conforme exemplo que se segue:

var data =  {
	    cmd     : 'save-record',
	    name    : 'form',
	    recid   : 10,
	    record  : {
	    	field1 : 'value1',
	    	fieldN : 'valueN'
		}
	};

$.ajax({
	  type: "POST",
	  url: "system/saveRecord",
	  data: data,
	  success: function(json) {console.log('Concluído: ' + json)},
	  dataType: "json"
	});

Pois bem, estes dados são enviados ao servidor e acionam meu método saveRecord.

Eu gostaria de saber como vc envia seu código para o server?

Aí entramos na segunda questão, o server side.

Em seu código vc nos mostrou o seguinte:

@Path("/mockSearch")  
@Consumes("application/json")  
public void teste(HotelPriceRequest request) throws IOException {  
      //IMPLEMENTACAO  
}

A minha dúvida é em relação à classe HotelPriceRequest, ela implementa ou estende algo?

E depois disto, como é utilizado o GSON? Como funciona a deserialização de teu objeto?

No exemplo de JSON que passei acima eu consigo os valores de cmd, name e recid. O bicho pega quando tento recuperar o que está na lista de record.

Agradeço qualquer ajuda!

Lucas_Cavalcanti

o código do $.ajax que vc mandou não vai enviar um JSON, e sim form parameters… daí vc pode usar o jeito normal de popular os parâmetros, sem o @Consumes.

Mas daí o json teria que ter os atributos mais ou menos assim:

{
   "request.cmd" : "save-record",
   "request.name": "form",
    ....
}
henriquemeira

Certo. Modifiquei o meu código $.ajax para o seguinte:

var data =  {  
        cmd     : 'save-record',  
        name    : 'form',  
        recid   : 10,  
        record  : {  
            field1 : 'value1',  
            fieldN : 'valueN'  
        }  
    };  
  
$.ajax({  
      type: "POST",  
      url: "system/saveRecord",  
      data: JSON.stringify(data),
      contentType:'application/json',
      success: function(json) {console.log('Concluído: ' + json)},  
      dataType: "json"  
    });

E me parece que agora está correto, pois inclusive o @Consumes não retorna erro 415.

A minha dúvida está em como receber isto no Controller, pois Map tenho o retorno de que a classe é abstrata e não pode ser instanciada. HashMap diz que não os nomes de parametros. String o resultado é nulo.

Tem alguma dica sobre como receber este objeto JSON no controller?

Eu preciso receber via JSON pois eu não conheço a estrutura de dados que irá chegar ao servidor, quero dizer, eu não posso colocar os parametros nome a nome, pois eu não sei quais são estes nomes.

Ou vc está dizendo para montar o JSON com “request.<NOME DO CAMPO” e ter um parametro de nome request no Controller?

Grato.

Lucas_Cavalcanti

no caso de usar o @Consumes, vc talvez precisaria escrever um converter pra Map.

Crie uma classe que estende JsonDeserializer e anote-a com @Component.

e veja se passa por ela…

se quiser tentar fazer a implementação, eu posso te ajudar.

henriquemeira

Lucas Cavalcanti:
no caso de usar o @Consumes, vc talvez precisaria escrever um converter pra Map.

Crie uma classe que estende JsonDeserializer e anote-a com @Component.

e veja se passa por ela…

se quiser tentar fazer a implementação, eu posso te ajudar.

Ok, vou dar início. Aviso em breve o resultado.

henriquemeira

Depois que vc deu a dica para estender JsonDeserializer, eu fiz alguns testes com a classe GsonDeserialization, já que a primeira deu algum erro por conta do jettison… enfim.

Eu consegui observar que o meu JSON completinho está chegando ao parametro do método.

Agora me diga uma coisa: o q eu faço com este parametro? Já que a “deserialização” é realizada quando ele recebe o valor.

Como posso dar continuidade ao meu trabalho apenas com este parametro? É possível?

grato.

Lucas_Cavalcanti

O json chega como string ou como JsonObject?

henriquemeira

Agora que perguntou, troquei o parametro para JsonObject ao invés de GsonDeserialization e estou conseguindo receber a estrutura completa do JSON.

Agora vou dar uma estudada sobre o JsonObject e tentar recuperar meus dados “genéricos” a partir dele.

Obrigado.

henriquemeira

Fechando o asunto…

Consegui receber a informação que precisava via parametro.

Resumidamente:

em web.xml habilitei os pacotes de deserialização via GSON conforme segue:

<context-param>  
        <param-name>br.com.caelum.vraptor.packages</param-name>  
        <param-value>br.com.caelum.vraptor.deserialization.gson,
        br.com.caelum.vraptor.serialization.gson</param-value>  
    </context-param>

No método do controller, anotado com @Consumes recebo um parametro JsonObject e percorro seu conteúdo “mapeando” as informações de acordo com o que vou precisar, conforme exemplo abaixo:

@Consumes("application/json") 
public void saveRecord(JsonObject json){
	
	Set<Entry<String, JsonElement>> entrySet = json.entrySet();
	
	RecordSet recordSet = extractRecordSet(entrySet); // RecordSet é um objeto que utilizo para armazenar os dados extraídos do JSON.
                                                                                    // A grande jogada aqui é percorrer o resultado de entrySet do JsonObject.
	
	...
	
}

E por fim, a transmissão do JSON do lado cliente para o servidor foi feito conforme o exemplo abaixo:

$.ajax({
		type : "POST",
		url : url_post, // URL do Controller
		data : JSON.stringify(data), // data = dados em formato JSON
		contentType : 'application/json',
		success : function(json) {
			console.log('Concluído: ' + json)
		},
		dataType : "json"
	});

Obrigado pelas dicas e pela força!

Criado 14 de agosto de 2013
Ultima resposta 19 de dez. de 2013
Respostas 26
Participantes 3