I am implementing simple RESTful webservice using Jersey API. My server project is hosted on Apache Tomcat ver 6.0 and it contains asm-3.0.jar, jersey-bundle-1.9.1.jar and jsr311-api-1.1.1.jar.
I have two resource classes. One is UserItemsResource which is intended to represent collection of UserItem objects. The other one is UserItemResource which represents a single UserItem resource.
Below is code for UserItemsResource class:
#Path("/useritems")
public class UserItemsResource {
#Context
UriInfo uriInfo;
#Context
Request request;
#Path("{userId}")
public UserItemResource getUserItemResource(#PathParam("userId") long userId) {
return new UserItemResource(uriInfo, request, userId);
}
}
The UserItemResource class:
public class UserItemResource {
#Context
UriInfo uriInfo;
#Context
Request request;
private long userId;
public UserItemResource(UriInfo uriInfo, Request request, long userId) {
this.uriInfo = uriInfo;
this.request = request;
this.userId = userId;
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public UserItem getUserItem() {
return new UserItem(userId, 'M', "Pawan");
}
}
And the UserItem class:
#XmlRootElement
public class UserItem {
private long userId;
private char sex;
private String displayName;
public UserItem() {
}
public UserItem(long userId, char sex, String displayName) {
this.userId = userId;
this.sex = sex;
this.displayName = displayName;
}
public long getUserId() {
return userId;
}
public char getSex() {
return sex;
}
public String getDisplayName() {
return displayName;
}
public void setUserId(long userId) {
this.userId = userId;
}
public void setSex(char sex) {
this.sex = sex;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}
When I invoke the resource (like /useritems/101), I am getting following response from server.
HTTP/1.1 200 OK
Date: Wed, 24 Oct 2012 11:30:35 GMT
Transfer-Encoding: chunked
Content-Type: application/json
Server: Apache-Coyote/1.1
{
"displayName": "Pawan",
"sex": "77",
"userId": "101"
}
You can see that the value for "sex" attribute is generated as "77", which is ASCII equivalent of character 'M'. I believe this should come as "M" only, so that my client code can successfully parse it back to 'M'. I am using Jackson API (ver 2.0.2) to parse the json entity in the server response back to object of UserItem class.
Am I missing something? Or is this a bug?
Jersey supports few JSON notations and each one of them has a slightly different convention on how the resulting JSON should look like. You can see the difference between notations in this JavaDoc. The default one is MAPPED which put quotes around numbers in JSON output as you've already found out.
There are two things you can do:
turn on the Jackson POJO support which uses the Jackson library to create JSON output (maybe this would be a better option if you're using Jackson on the client side as well). There is also an example how to use it - JacksonJsonProvider.
use NATURAL JSON notation which handles numbers as you'd expect. To do this you need to provide a custom ContextResolver and register it in your application. Examples how to achieve this can be found in Jersey User Guide (JSON Support - Configuration Options) or in one of the samples json-from-jaxb (see JAXBContextResolver).
Related
I have a problem during the deserialization of a response. Let's suppose I have this response from third party using webclient .
Response :
{
"name":"FirstName",
"type":"Steel",
"Fee":{
"id":"1234",
"name":"FeeFirstName"
},
"address":"2nd Street"
}
This is how my pojo classes looks like
public class Fee{} //generic OR empty class
public class Foo{
private String name;
private String type;
private Fee fee;
private String address;
}
My webclient get response code :
#Autowired
private WebClient fooWebClient;
public Foo getFoo()
{
try{
return fooWebClient.get()
.uri(uriBuilder -> uriBuilder.path("/foo/fee").build("123"))
.header(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Foo.class)
.block();
}catch(Exception e){throw new ApiClientException(e.getMessage());}
}
The above webclient getFoo() code is not giving me the full response, the Fee is coming blank stating "Class has no fields". Rest of the values are coming properly in response.
Fee needs to be empty as any other object can also come.
Please let me know how to deserialize the whole response.
You don't need the Fee class, you can get rid of it entirely and use a Map instead:
public class Foo {
private String name;
private String type;
private Map<String, Object> fee;
private String address;
}
We cannot dynamically create POJO and hence we are left with two options.
Add necessary fields to the 'Fee' class (If you know Fee structure upfront)
If you are not sure about the 'Fee' structure go for Map.
Because spring integrates Jackson you can create a custom Jackson JSON Deserializer for the Fee class that gives you more control:
#JsonDeserialize(using = FeeDeserializer.class)
public class Fee {
private String id;
private String name;
public Fee(String id, String name) {
this.id = id;
this.name = name;
}
}
import com.fasterxml.jackson.*;
public class FeeDeserializer extends JsonDeserializer<Fee> {
#Override
public Fee deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
ObjectCodec codec = jsonParser.getCodec();
JsonNode tree = codec.readTree(jsonParser);
JsonNode id = tree.get("id");
JsonNode name = tree.get("name");
return (id != null && name != null) ? new Fee(id.asText(), name.asText()) : null;
}
}
For more details see
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.json.jackson.custom-serializers-and-deserializers
https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-codecs-jackson
I have the following REST API:
#ResponseBody
#PostMapping(path = "/configureSegment")
public ResponseEntity<ResultData> configureSegment(#RequestParam() String segment,
#RequestBody #Valid CloudSegmentConfig segmentConfig
) {
CloudSegmentConfig:
#JsonProperty(value="txConfig", required = true)
#NotNull(message="Please provide a valid txConfig")
TelemetryConfig telemetryConfig;
#JsonProperty(value="rxConfig")
ExternalSourcePpdkConfig externalSourcePpdkConfig = new ExternalSourcePpdkConfig(true);
TelemetryConfig:
public class TelemetryConfig {
static Gson gson = new Gson();
#JsonProperty(value="location", required = true)
#Valid
#NotNull(message="Please provide a valid location")
Location location;
#Valid
#JsonProperty(value="isEnabled", required = true)
#NotNull(message="Please provide a valid isEnabled")
Boolean isEnabled;
Location:
static public enum Location {
US("usa"),
EU("europe"),
CANADA("canada"),
ASIA("asia");
private String name;
private Location(String s) {
this.name = s;
}
private String getName() {
return this.name;
}
}
When I'm trying to send the following JSON:
{
"txConfig": {
"location": "asdsad"
}
}
The API return empty response 400 bad request, while I expect it to validate the location to be one of the ENUMs of the class. I also expect it to validate the isEnable parameter while it doesn't although I added all possible annotation to it..
Any idea?
Use #Valid annotation on TelemetryConfig telemetryConfig and no need to use #Valid on the field of TelemetryConfig class.
#Valid
TelemetryConfig telemetryConfig;
And for enum subset validation you can create a customer validator with annotation and use it.
A good doc about this Validating a Subset of an Enum
I am busy with a Dropwizard application and need to have an array injected to a POJO as one of the parameters of a put method. Unfortunately the array is not properly handled which results in a Bad Request response. To illustrate the JSON passed by the frontend looks like:
{
"name": "Jon",
"surname": "Doe",
"children": ["Chris", "Dave", "Sam"]
}
And my Java representation:
public class Person{
private String name;
private String surname;
private List<String> children;
public Person(){
}
#JsonProperty
public String getName(){
return name;
}
#JsonProperty
public void setName(String name){
this.name=name;
}
#JsonProperty
public String getSurname(){
return surname;
}
#JsonProperty
public void setsurname(String surname){
this.surname=surname;
}
#JsonProperty
public List<String> getChildren(){
return children;
}
#JsonProperty
public void setChildren(List<String> children){
this.children=children;
}
}
And in my resource class:
#PUT
#Timed
#UnitOfWork
#Path("/{userid}")
public Response getData(#PathParam("userid") LongParam userId,
Person person) {
// Do some stuff with the person
}
How can I properly handle the deserialization of the array in the JSON?
EDIT
I am using an angular front-end and I am invoking the method as follows:
function(json){
return $http({
url: API_URL.people+"/update/personID",
method: "PUT",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
data: json
});
}
Where the json argument contains the name, surname and children as above.
Looks like the GET service is defined incorrectly. It shouldn't have Person defined.
As per http method definition, the GET http method can't have body. So you can't have Person as the input parameter.
If you need to send Person to service, you may need to change the http method to POST or something else (like PUT) based on your requirement.
#GET
#Timed
#UnitOfWork
#Path("/{userid}")
public Response getData(#PathParam("userid") LongParam userId) {
// Do some stuff with the person
}
Turns out the code I provided words like a charm. Upon further investigation I made a mistake in the javascript object which got converted and sent as JSON which caused the error.
I implemented this POST operation in Jax-RS and it is working fine, however I am sure I can send the body in other way somehow. Any hint to simplify it? (By receiving an object for instance?)
Thanks!
#POST
#Path("updateUser/{name}/{surname}/{address}")
#Produces(MediaType.TEXT_PLAIN)
public Response updateUser(#FormParam("user") String name,
#FormParam("surname") String surname,
#FormParam("address") String address) throws UnknownHostException {
User user;
user = new CorporateUser(name, surname, address);
usersService.updateUser(user);
return Response.ok(user).build();
}
You can pass json string of object by using #consumes annotaion.
#POST
#Path("/updateUser")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.TEXT_PLAIN)
public Response updateUser(User bean) {
usersService.updateUser(user);
return Response.ok(user).build();
}
Add a JSON provider like Jackson to your project.
#POST
#Path("updateUser")
#Consumes(value = { MediaType.APPLICATION_JSON })
#Produces(value = { MediaType.APPLICATION_JSON })
public Response updateUser(NewCorporateUserRequest req) throws UnknownHostException {
User user;
user = new CorporateUser(req.getName(), req.getSurname(), req.getAddress());
usersService.updateUser(user);
return Response.ok().entity(user).type(MediaType.APPLICATION_JSON).build();
}
public class NewCorporateUserRequest implements java.io.Serializable {
private String name;
private String surname;
private String address;
... Getters and Setters.
}
I cannot seem to get this to work for me ,I have seen this in other posts and was hoping someone may be able to spot what im doing wrong.I am trying to get the body of a request to this rest api but cannot seem to pull back what i need and just get null in the string below.
#POST
#Path("/SetFeeds")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public String setFeed(#PathParam("name")String name2, #QueryParam("name") String name,#Context UriInfo uriInfo){
MultivaluedMap<String,String> queryParams = uriInfo.getQueryParameters();
String query = uriInfo.getRequestUri().getQuery();
String response = queryParams.getFirst("name");
return response;
}
A method parameter to accept the body of the request should not be annotated with anything (except in few cases like individual form params and multipart). So to get the raw JSON, you could simply add a String parameter
public String setFeed(#PathParam("name")String name2,
#QueryParam("name") String name,
#Context UriInfo uriInfo,
String jsonBody){
Or if you want to do the more common thing and use POJOs (so you don't need to parse the JSON yourself), you should look at this answer
Great answer, but I would like to add that you can use an object instead of a String and the Jackson of REST will take care the transformation without any further definition.
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public String setFeed(#PathParam("name")String name2,
#QueryParam("name") String name,
MyJson json,
#Context UriInfo uriInfo){
MultivaluedMap<String,String> queryParams = uriInfo.getQueryParameters();
String query = uriInfo.getRequestUri().getQuery();
String response = queryParams.getFirst("name");
return response;
and a pojo as a json object:
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyJson{
private String name;
public MyJson(){}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}