I am using Retrofit and Kotlin to build an API client. For accessibility, I will provide examples in Java.
There are a few methods in this API that require a JSON body with only one parameter. I made an annotation #SingletonBody(String) that tells a call adapter to wrap the argument in a Map<String,Object>.
For example, to rename an account we PUT a A simple json body.
curl -X PUT https://api.example.com/account/whatever -d '{ "name": "the new name for the account" }'
I want to pass a String name argument for simplicity rather than a Map. I could accomplish this by creating a default method that delegates to another method.
public interface AccountApi {
// Inconvenient delegate
#PUT("/accounts/{accountId}")
Call<Void> renameAccount(#Path("accountId") String accountId, #Body Map<String,Object> body);
// Convenient wrapper
default Call<Void> renameAccount(String accountId, String name) {
return renameAccount(accountId, Collections.singletonMap("name", name));
}
}
This felt messy to me. My #SingletonBody annotation allows me to declare only one method.
public interface AccountApi {
#PUT("/accounts/{accountId}")
Call<Void> renameAccount(#Path("accountId") String accountId,
#Body #SingletonBody("name") String name);
}
TL;DR
My #SingletonBody annotation necessarily implies Retrofit's #Body annotation. How can I tell the complier to infer #Body from #SingletonBody such that I don't need to write both? Perhaps with an annotation preprocessor?
My instinct was to extend Retrofit's annotation, but the JVM (Kotlin too) does not allow annotations to have explicit superclasses or superinterfaces.
Related
I have a REST service that has a POST endpoint. This POST endpoint needs to receive an object (TravelRequisitionFormDTO) as part of its body:
#POST
#Path("/request")
#ApiOperation(value="Request a trip. Submit the trip request.")
#ApiResponses({
#ApiResponse(code=200, message="Success"),
#ApiResponse(code=404, message="Not Found")
})
#Produces({ MediaType.APPLICATION_JSON })
public Response getSubmitTrip(#HeaderParam("Authorization") String token, #ApiParam(required = true) TravelRequisitionFormDTO travelRequisitionFormDTO, #Context HttpServletRequest request) {
...
}
So when I call the endpoint, I get the following error:
<p><b>message</b> <u>org.codehaus.jackson.map.JsonMappingException: Conflicting setter definitions for property
"contactMethods": utility.dataobjects.ContactObject#setContactMethods(1 params) vs
utility.dataobjects.ContactObject#setContactMethods(1 params)</u></p>
<p><b>description</b> <u>The request sent by the client was syntactically incorrect
(org.codehaus.jackson.map.JsonMappingException: Conflicting setter definitions for property
"contactMethods": utility.dataobjects.ContactObject#setContactMethods(1 params) vs
utility.dataobjects.ContactObject#setContactMethods(1 params)).</u></p>
The reason for the error is because the TravelRequisitionFormDTO has a member variable (called ContactObject) that has two methods that are overloaded. So when it tries to convert the JSON body to JAVA, I guess it does not know which overloaded method to use. I think it sees it as ambiguous.
public void setContactMethods(ArrayList list)
and
public void setContactMethods(String[] list)
I don't want to change ContactObject if possible, because it is used in a number of other places.
Question
Is there any way I can resolve this? i.e. so that the JSON body can be converted successfuly into the Java object?
you can keep single property accepting List. and your Contractobject can consume both Array & List.
You could annotate one setter with Jackson #JsonSetter annotation:
#JsonSetter
public void setContactMethods(ArrayList list)
Make sure that you use right package. In your case it would be org.codehaus.jackson.annotate.JsonSetter like you can see in the error message. It might happen that you have also com.fasterxml.jackson.annotation.JsonSetter in the classpath so you have to be careful not to mix it.
Alternatively you can use #JsonProperty instead.
I have an interface that has a static key. And I wanted to retrieve this key from a string file, but if I just put R.string.key, it shows incompatible types, because it retrieves the integer value, and if I put R.string.key + "", it becomes a string but retrieves the String file id, it would be best to use getResources.getString (R.string.key), but there is no way to use the getResources.getString method in the Interface.
Working:
public interface NotificacaoService {
#Headers({"34853485734",
"Content-Type:application/json"}) #POST("send")
Call<NotificacaoDados> salvarNotificacao(#Body NotificacaoDados notificacaoDados);
}
I want to leave it like this:
public interface NotificacaoService {
#Headers({getResources.getString(R.string.key),
"Content-Type:application/json"}) #POST("send")
Call<NotificacaoDados> salvarNotificacao(#Body NotificacaoDados notificacaoDados);
}
Sorry, what you want is not possible. Annotation parameters need to be constants.
One solution is to switch from string resources to BuildConfig. Use buildConfigField in your Gradle script to define your key (buildConfigField "String", "API_KEY", "\"34853485734\""). Then, you can reference that generated constant in your interface (e.g.,BuildConfig.API_KEY`).
Alternatively — if this interface is for Retrofit — you could add your headers via an OkHttp interceptor, instead of via a #Headers annotation.
I'm new to Jersey. So, please pardon any mistake.
I'm trying to setup a simple REST ws.
There is a method name getConnectedMHubs that have one required parameter thingID and two optional parameters: time and delta.
Is it possible to use the same method name for the two type of calls, with and without the optional parameters?
I tried to specify two pathes but got a ModelValidationException, that says:
A resource model has ambiguous (sub-)resource method for HTTP method
GET and input mime-types as defined by"#Consumes" and "#Produces"
annotations at Java methods public ...
Code sample:
#Path("/api")
public class RendezvousWebService {
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("connectedmhubs/{mhubid}")
public String getConnectedThings(#PathParam("mhubid") String strMHubID) {
// ...
return "{}";
}
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("connectedmhubs/{mhubid}/{time}/{delta}")
public String getConnectedThingsExtended(#PathParam("mhubid") String strMHubID, #PathParam("time") long timestamp, #PathParam("delta") long delta){
// ...
return "{}";
}
}
Using the #Path makes the params mandatory. You can get around this with regular expressions or you can use #QueryParam with #DefaultValue to roll the two methods into one.
Using a path pattern like this:
#Path("connectedmhubs/{mhubid}")
makes the path parameter mandatory. However, you can make use of regular expressions to overcome this limitation. See this link for details.
I want to use non spring bean class object as parameter for jersey web service class method. But it is giving missing dependency error at build time.
My code is:
#Component
#Path("/abcd")
public class ActorServiceEndpoint {
#POST
#Path("/test/{nonspringBean}")
#Produces(MediaType.APPLICATION_XML)
public void addActor(#PathParam("nonspringBean") MyNonSpringBeanClass nonspringBean){
}
}
The thing is path parameters come in String form. As per the specification, if we want the have a custom type be injected as a #PathParam, the custom class, should have one of three things:
A public static valueOf(String param) that returns the type
A public static fromString(String param) that returns the type
Or a public constructor that accepts a String
Another option implement a ParamConverter. You can see an example here.
If you don't own the class (it's a third-party class that you can't change) then your only option is to use the ParamConverter/ParamConverterProvider pair.
In either of these cases you'll want to construct the instance accordingly by parsing the String either in the constructor or in one of the above mentioned methods. After doing this, the custom type can be made a method parameter with the annotation.
The same holds true for other params, such as #FormParam, #HeaderParam, #QueryParam, etc.
It would help if you gave a bit more details of the error you're getting, but I see two problems with your code snippet:
The correct Spring annotation is #PathVariable, #PathParam is probably from another package. This doesn't apply as I guess you're using JAX-RS, not Spring annotations.
I'm not sure what converters are applied to path variables, but in any case it would need to have one for MyNonSpringBeanClass. I would take a String parameter and then instantiate MyNonSpringBeanClass myself in the function body.
I am using jersey to implement REST api service in my product. To get the form parameter value I am trying to using #FormParam annotation inside my method. But in eclipse it throws error "The annotation #FormParam is disallowed for this location".
How do I fix this error?
Do not put annotations inside methods. Annotations are usually placed before:
Classes
Methods and constructors
Parameters of methods
(however, more possibilities are available)
In your case, annotate a parameter:
public String addUser(#FormParam("emailid") String emailid,
#FormParam("password") String password) {
...
}
that makes request.getParameter(...) unneeded.
Check a full example.