How to receive a non-entity class as param on POST method? - java

Controller method:
#PostMapping("/nova")
#ResponseStatus(HttpStatus.CREATED)
public String adicionarTemp(#RequestBody TempCaptacao temp) {
Param Class:
public class TempCaptacao{
String dsNome;
...
List<TempResponsavel> listaResponsavel;
public TempCaptacao() {
}
SubParam Class:
public class TempResponsavel{
Long id;
String dsNome;
...
public TempResponsavel() {
}
When Angular calls this method, passing a JSON (as below) my springs returns this error:
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of com.spin.spincare.controller.MovAtendimentoCaptacaoController$TempCaptacao (although at least one Creator exists): can only instantiate non-static inner class by using default, no-argument constructor; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.spin.spincare.controller.MovAtendimentoCaptacaoController$TempCaptacao (although at least one Creator exists): can only instantiate non-static inner class by using default, no-argument constructor
at [Source: (PushbackInputStream); line: 1, column: 2]]
JSON:
{
"dsNome":"teste",
"dsGenero":"Masculino",
"dtNascimento":"2000-12-30T02:00:00.000Z",
"idConvenio":16,
"dsCep":"12321321",
"dsEndereco":"teste",
"dsEstado":"SC",
"dsCidade":"",
"dsBairro":"",
"dsComplemento":"",
"listaResponsavel":[
{
"id":1,
"dsNome":"teste",
"nrCelular":"231321312213",
"dsEmail":"",
"dsGrau":"Pai"
}
]
}
I've those empty constructor methods, but with a list inside my class, how can I pass this class as param to my POST method?
EDIT
Missing static definition on my TempCaptacao class..
Thanks!

After thinking about the error and what you have posted, I think what you are doing is this:
public class TempCaptacao {
// attributes etc
public TempCaptacao() {}
public class TempResponsavel { // inner class
// attributes etc
public class TempResponsavel() {}
}
}
Don't do that. Define the classes in your model separately, if for no other reasons than maintainability / readability / extendability.
I think JSON should be able to deserialize this if you remove the constructor from your inner class, but you're going to experience any number of issues doing something nonstandard like this.

Related

Jackson Deserialize with Subclasses

Ok, I know there are a bunch of similar questions, but nothing seems to work.
I have the following structure set up for my entities.
public abstract class MyAbstractClass {
// bunch of properties, getters, and setters that subclasses share
public abstract String getType();
}
public class MySubclass1 extends MyAbstractClass {
// a few unique properties, getters, and setters
public String getType() {
return "Type_1"; //always the same for each instance of MySubclass1
}
}
public class MySubclass2 extends MyAbstractClass {
// a few unique properties, getters, and setters
public String getType() {
return "Type_2"; //always the same for each instance of MySubclass2
}
}
In my controller, I try to map a request to the following method.
public #RequestBody MyAbstractClass saveObject(#RequestBody MyAbstractClass mac) {
// call model to save object
}
I would like to use 1 controller method versus separate ones for the 2 entities. But using the above results in the following.
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of path.to.my.entity.MyAbstractClass, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
Makes sense.
TRY 1
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="implementingClass")
public abstract class MyAbstractClass
What I think it does - adds a metadata implementingClass property that will store the subclass class.
What the result is.
Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'implementingClass' that is to contain type id (for class path.to.my.entity.MyAbstractClass)
Tried with "class" instead of "implementingClass" for the property and got similar results.
TRY 2
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#Type(name="MySubclass1", value=MySubclass1.class),
#Type(name="MySubclass2", value=MySubclass2.class)
})
public abstract class MyAbstractClass
What I think it does - uses the defined name to do some sort of wrapping thing.
What the result is.
Could not resolve type id 'myUuid' into a subtype of [simple type, class path.to.my.entity.MyAbstractClass]
Same results even when adding #JsonTypeName("MySubclass1") and #JsonTypeName("MySubclass2") to the 2 subclasses.
Other Tries
I tried a lot. Nothing works. Won't include everything here.
I feel like there should be a simple way to do this, but I just keep on configuring things incorrectly.
I feel like the getType could maybe be leveraged, but I don't want to add an actual property for type (it's just a helper method). Also I would like to do this with annotations versus other options.
Thank you.
I figured it out but I guess I'll answer in case anyone else has this problem.
I added a type property to my subclasses instead of just a helper method (one example included below).
public class MySubclass1 extends MyAbstractClass {
#Transient
private final String type = "TYPE_1";
public String getType() {
return type;
}
}
Then I did the following for my abstract superclass.
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
#JsonSubTypes({
#Type(name="TYPE_1", value=MySubclass1.class),
#Type(name="TYPE_2", value=MySubclass2.class)
})
public abstract class MyAbstractClass
When providing the JSON, I was sure to include the type. I won't include this because it's weird knockout insanity.
It's not great. But it worked.

How to inject the variable in the abstract class while unit testing the subclass?

I have an abstract class BaseTemplate and multiple classes extending it. In one of the concrete class(SmsTemplate extends BaseTemplate), we have a private variable Gson. We have the same private variable(Gson) in the abstract class as well.
While unit tesing the concrete class, methods in the abstract class is getting called from the concrete class. In my Unit test, I am using Whitebox.setInternalState(smsTemplateObj, gsonObj); to inject the Gson object into the private members of SmsTemplate and BaseTemplate but the Gson is getting injected only in the subclass. In abstract class, its NULL, meaning not injected. Below is the implementation.
Can someone please tell how to inject the Gson object in the abstract class?
abstract class BaseTemplate{
private Gson gson;//Here its not getting injected
protected String getContent(Content content){
return gson.toJson(content); // ERROR - gson here throws NPE as its not injected
}
}
class SmsTemplate extends BaseTemplate{
private Gson gson;//Here its getting injected
public String processTemplate(Content content){
String strContent = getContent(content);
...
...
gson.fromJson(strContent, Template.class);
}
}
Whitebox.setInternalState() method will only set the value of the first field it encounters going up through the hierarchy of the object you pass. So once it finds gson field in your subclass, it won't look further and won't change the superclass field.
There are two solutions for this case:
Change the variables names. If the variables have different names, you can simply invoke Whitebox.setInternalState() twice, one for each variable.
Set the field manually using reflection. You can also just set the field without Mockito's help using something like the following snippet.
Snippet:
Field field = smsTemplateObj.getClass().getSuperclass().getDeclaredField("gson");
field.setAccesible(true);
field.set(smsTemplateObj, gsonObj);
You need a second abstraction layer:
abstract class BaseTemplate{
// private Gson gson;// no Gson here
protected String getContent(Content content){
// do what you want without Gson
}
}
abstract class BaseTemplateWithGson extends BaseTemplate{
protected Gson gson;
#Override
protected String getContent(Content content){
return gson.toJson(content);
}
}
class SmsTemplate extends BaseTemplateWithGson {
public String processTemplate(Content content){
String strContent = getContent(content);
...
...
gson.fromJson(strContent, Template.class);
}
}

How to register a static class in Jersey?

I have a class of which only static methods are to be accessed via #path annotations and which does not have a public constructor. My simpilified program is:
#Path("")
static class MyStaticClass
{
private MyStaticClass() {...}
#Get #Path("time")
static public String time()
{
return Instant.now().toString();
}
}
Running and calling "time" gives me the following error:
WARNUNG: The following warnings have been detected: WARNING: HK2 service reification failed for [...] with an exception:
MultiException stack 1 of 2
java.lang.NoSuchMethodException: Could not find a suitable constructor in [...] class.
Sorry, according to the JSR, paragraph 3.1.2
Root resource classes are instantiated by the JAX-RS runtime and MUST
have a public constructor for which the JAX-RS runtime can provide all
parameter values. Note that a zero argument constructor is permissible
under this rule.
You can use the Adapter design pattern and create JAX-RS resource (POJO with #Path) which simply delegates to your static class. This would be very easy to understand for those coming behind you.
The #Path annotation is designed to define a resource at the class level. The method to execute isn't controlled by #Path, but by #GET, #POST, #PUT, #HEAD, etc... with #GET as the desired operation in your case.
Your class for the "time" resource should look like this:
#Path("/time")
public class TimeResource {
#GET
public static String time(){
return Instant.now().toString();
}
}
You could theoretically define each function as a static nested class within one "main" class:
public class MyResource{
#Path("/time")
public static final class TimeResource {
#GET
public static String do(){
return Instant.now().toString();
}
}
#Path("/doSomethingElse")
public static final class DoSomethingElseResource {
#GET
public static String do(){
// DO SOMETHING ELSE
}
}
}
Though I don't know if that would work, you'd have to try it. I don't think there's much advantage in having them all in one class like that, though.

how to deal with interfaces at jackson?

I am dealing with this problem. I have this class:
public class SemaphoreResponse {
ISemaphore semaphore;
StatusHolder statusHolder;
public SemaphoreResponse() {
super();
}
// Getters and setters
}
I want to convert my json string to that class, and it throws me this exception
org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.despegar.henry.automation.services.semaphoreservice.response.ISemaphore, problem: abstract types can only be instantiated with additional type information
at [Source: java.io.StringReader#17a5f5a5; line: 1, column: 2] (through reference chain: com.despegar.henry.automation.services.semaphoreservice.response.SemaphoreResponse["semaphore"])
at org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:163)
at org.codehaus.jackson.map.deser.StdDeserializationContext.instantiationException(StdDeserializationContext.java:233)
at org.codehaus.jackson.map.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:60)
at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:299)
at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:414)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:697)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
So, i understand that this is happening because the "semaphore" attribute, which is an interface, so into that interface i know i have to add #JsonDeseralize like this
#JsonDeserialize(as = [class-name].class)
public interface ISemaphore {
public abstract String getId();
public abstract void setId(String id);
public abstract String getOwnerUserId();
}
But this is my problem. The attribute semaphore from SemaphoreResponse doesn't use always same class. ergo, i have two different classes called "MainSemaphore" and "ExecutionSemaphore", which both implements the interface ISemaphore. So, at the time for deserealization i want to pass the class that i want the interface adapts for as a parameter.
It would be something like
#JsonDeserialize(as = MainSemaphore.class) or #JsonDeserialize(as = ExecutionSemaphore.class) depending the case
How could i do that? I would appreciate your help
Besides full polymorphic handling, described by excellent article by ProgrammerBruce, there is also a simpler way to just support simple interface/impl, one-to-one, case: register mapping via module:
SimpleModule m = new SimpleModule(...);
m.addAbstractTypeMapping(ISemaphore.class, SemaphoreImpl.class);
mapper.registerModule(m);
and this would instruct Jackson to always deserialize things declare as ISemaphore using concrete class SemaphoreImpl.

Use #JacksonInject with #JsonCreator on a top level map

With Jackson json library, it is possible to deserialize object through the use of the #JsonCreator, and be given the "top level" map representing the input json, as follows:
class MyClass {
final int field;
#JsonCreator
public MyClass(Map<String, Object> map) {
this.field = (int) map.get("theInt");
}
}
or even on a static factory method:
class MyClass {
final int field;
public MyClass(int theInt) {
this.field = theInt;
}
#JsonCreator
static MyClass create(Map<String, Object> map) {
return new MyClass((int) map.get("theInt"));
}
}
The previous examples can process the following kind of json input:
{
"key1":"value1",
"key2":"value2",
"key3":"value3"
}
This is particularly useful in my case because I would like to deserialize a json which structure I don't know. Being given access to what I call the "top level map" makes things simple.
I would like to deserialize my objects this way as it also allows to create immutable objects, instead of using #JsonAnySetter which doesn't permit this, and #JsonProperty which I can't use as I don't know the properties name in advance as I mentioned earlier.
Then to go further, I would like to inject some configuration in my factory method, and Jackson allows this through the use of #JacksonInject and a call to withInjectableValues(InjectableValues) on the ObjectMapper.
This is eventually the kind of code I would like to use:
class MyClass {
final MyField[] myFields;
public MyClass(MyField... myFields) {
this.myFields = myFields;
}
#JsonCreator
static MyClass create(#JacksonInject("conf") Conf conf, Map<String, Object> map) {
MyFields[] myFields;
// initialize myFields (or any other object) based on the content of map
// and also make use of the inject conf
return new MyClass(myFields);
}
}
Unfortunately, Jackson throws the following kind of exceptions:
when trying the trick on the constructor
JsonMappingException: Argument #1 of constructor [constructor for MyClass, annotations: {JsonCreator=#JsonCreator()}] has no property name annotation; must have name when multiple-paramater constructor annotated as Creator
when trying the trick on the factory method
JsonMappingException: Argument #1 of factory method [method create, annotations: {JsonCreator=#JsonCreator()}] has no property name annotation; must have when multiple-paramater static method annotated as Creator
Does anyone know how I could solve the problem?
To sum up the requirements, I need:
access to the top level map (don't know the json property names in advance)
to create an immutable object (so can't use #JsonAnySetter)
to inject some conf to the #JsonCreator decorated constructor or factory method
I cannot change the json input format, which looks like this:
{
"key1":"value1",
"key2":"value2",
"key3":"value3"
}
[EDIT]
This is a known issue: http://jira.codehaus.org/browse/JACKSON-711 (not fixed yet)
Right, you would like to both use "delegating" creator (single argument, into which JSON input is first bound) -- different from "property-based" creator where set of named parameters are passed -- and injectable value(s). This should ideally work, but I think it might not work currently.
I think there is a Jira entered for this, so you can check it (http://jira.codehaus.org/browse/JACKSON) out.
Just to make sure: are you using version 1.9.2? There have been some fixes in this are since 1.9.0; which at least would give better error message.

Categories

Resources