How to send json with GET params via Spring Boot / Tomcat? - java

so currently I'm working on a project where we have product objects which in turn contain "Origin" objects (containing region: String and country: String).
What I'm trying to do is a RestController which takes in an optional Origin object and does something with it (e.g. logs it).
This is what I have right now:
#GetMapping("search")
public Page<Wine> getProductByStuff(
#RequestParam(required = false) Origin origin,
/* other attributes */) {
log.info(origin); // it has a proper toString method.
}
There are two problem with this approach. First of all, when I send a request like:
http://[...]/search?origin={"region":"blah","country":"UK"}
or even the html converted string like:
http://[...]/search?origin={%22region%22:%22blah%22%44%22country%22:%22UK%22}
... it says
Invalid character found in the request target [/api/products/search?origin={%22region%22:%22blah%22%44%22country%22:%22DE%22}]. The valid characters are defined in RFC 7230 and RFC 3986.
Afaik the only valid characters Tomcat has that I need are {}. All others I've replaced with the html encoded chars and it still doesn't work.
What I did to prevent this:
#Component
public class TomcatWebServerCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
TomcatConnectorCustomizer a = null;
factory.addConnectorCustomizers(connector -> {
connector.setAttribute("relaxedPathChars", "<>[\\]^`{|},\"");
connector.setAttribute("relaxedQueryChars", "<>[\\]^`{|},\"");
});
}
}
(See this, which is, by the way, deprecated (at least connector.setAttribute).)
This produced:
MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type '[censored].backend.model.Origin'.
My questions are:
(How) is it possible to configure Tomcat/Spring so that they can actually accept json in the url params?
How would I format it in e.g. Postman so that it would work? Currently I'm just converting special characters by hand in the params tab of Postman.

Here is what you need to do if you want to send it as json query param.
#RestController
public class OriginController {
#GetMapping("/search")
public void getOrigin(#RequestParam(value = "origin", required = false)
Origin origin) {
System.out.println(origin);
}
}
Register a converter
#Component
public class StringToOriginConverter implements
Converter<String, Origin> {
ObjectMapper objectMapper = new ObjectMapper();
#Override
public Origin convert(String source) {
try {
return objectMapper.readValue(source, Origin.class);
} catch (JsonProcessingException e) {
//You could throw some exception here instead for custom error
return null;
}
}
}
Sending from postman
Note
My answer is not debating whether you should use POST or GET as it is not what you have asked. It is just providing one option if you want to send some payload as query param

As mentioned, don't use JSON as a path parameter.
Directly use path parameters, and convert to Origin object.
#GetMapping("search")
public Page<Wine> getProductByStuff(
#RequestParam(required = false) String region,
#RequestParam(required = false) String country, /* other attributes */) {
Origin origin = new Origin(region, country);
log.info(origin); // it has a proper toString method.
}

Related

How extract response to String and save it to variable

Being new to Java/JSON/REST Assured topics, I would like to extract a parameter of "token": from a JSON response body as a String and store it as variable which I could take to some other classes and use there. However, I have tried it and have not found a way. Below is part of a code which I have created at the beginning in a same manner as other requests stored in this class, but this is the first one from which I need something from the response:
public FakeTokenVO fakeToken() {
String payload = "payloadthere";
return given(specBuilder.fakeTokenRequestSpecification()) .
body(payload)
.log().all()
.when()
.post(RestApiRoutes.FAKE_URI)
.then()
.log().all()
.extract()
.response()
.as(FakeTokenVO.class);
}
Don't mind about the payload and those VO classes as it is stored as data model somewhere else.
Response from the request made looks like this:
{
"createTokenResponse": {
"createTokenSuccess": {
"token": "token_with_somewhere_about_700_characters"
}
}
}
Here is how I have tried to modify it to get the part of response which I need later (the token to authorize other requests):
#Test
public void fakeToken()
{
String payload = "payloadthere";
String token = given(specBuilder.fakeTokenRequestSpecification())
.body(payload)
.log().all()
.when()
.post(RestApiRoutes.FAKE_URI)
.then()
.log().all()
.extract()
.response()
.body().path("createTokenResponse.createTokenSuccess.token");
System.out.print(token);
}
This test returns me a value which I needed, but I do not know how to implement it as a method instead of test. Please help how should I approach it? What am I missing there? I tried to search for answers, but I haven't found a solution yet or do not know how to implement it in my part of the code.
I assume that you can get your response as a String. So all you need to do is to parse your Json String. For that you can use any available Json parser. The most popular ones are Json-Jackson (also known as Faster XML) or Gson (by Google). Both are very well known and popular. (My personal preference is Jackson, but it is a matter of opinion).
However, For simplistic cases like this I wrote my own utility (a thin wrapper over Jackson library) that allows you to parse Json String very simply without learning relatively complex libraries. With my utility your code may look like this:
try {
Map<String, Object> map = JsonUtils.readObjectFromJsonString(jsonStr, Map.class);
Map<String, Object> innerMap = map.get("createTokenResponse");
Map<String, Object> innerMap2 = map.get("createTokenSuccess");
String token = innerMap.get("token");
} catch (IOException e) {
e.printStacktrace();
}
Or you can create your own classes such as
public class TokenResult {
String token;
//getter and setter
}
public class TokenHolder {
private TokenResult createTokenSuccess;
//setter and getter
}
public class TokenResponse {
private TokenHolder createTokenResponse;
//setter and getter
}
And than your code may look like this:
try {
TokenResponse response = JsonUtils.readObjectFromJsonString(jsonStr, TokenResponse .class);
String token = response.getCreateTokenResponse().getCreateTokenSuccess().getToken();
} catch (IOException e) {
e.printStacktrace();
}
Here is a Javadoc for JsonUtils class. This Utility comes as part of Open Source MgntUtils library written and maintained by me. You can get the library as maven artifact on Maven Central here or on the github (including source code and javadoc)

Jersey Conditional Pathing

I have a scenario where we support 2 different types of authenticated users (UserTypeA, UserTypeB), but they will never be used in the same server environment. Right now, we use 2 different url paths /path/usertypea/list vs /path/usertypeb/list. We would like to make them use the same path if possible, for example /path/list, and have an environment variable be the condition to know where to route the traffic. The parameters for each user type aren't exactly the same, there are some differences in how the data is organized. We're using Jersey.
I've tried a few things like Singleton classes: https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/user-guide.html#d0e2650 / https://stackoverflow.com/a/33585724/12183373 but it never routes the value, it just returns the name of the class instead of the JSON payload I'm expecting.
Here's some of the code:
#Path("/list")
public class GlobalSegmentServiceRouter {
#GET
#Produces("application/json")
public Class<?> findAll() {
boolean isUserTypeA = false;
if (isUserTypeA) {
return UserTypeAService.class;
} else {
return UserTypeBService.class;
}
}
}
Then I have 2 separate class files for the following:
#Singleton
public class UserTypeAService {
public List<String> findAll(/*Parameters for A*/) {
// Do work here for User Type A
}
}
#Singleton
public class UserTypeBService {
public List<String> findAll(/*Parameters for B*/) {
// Do work here for User Type B
}
}
When I try and hit the endpoint, this is the response I get:
"com.test.services.UserTypeAService"
Any suggestions on how to accomplish this?
add some flag for checking which kind of user is logged in to a custom principal impl. Then you can inject the current user and then call UserTypeAService.findAll or UserTypeBService.findAll in your method.
#GET
#Path("/path/list")
public String yourMethod(#Context SecurityContext securityContext)

How to send Integer value in the form of JSON format and recieve in REST Controller?

I have one REST Controller where I have written this code
#PostMapping(value = "/otp")
public void otp(#RequestBody Integer mobile) {
System.out.println(" Mobile = "+mobile);
}
And I am calling this method from Postman with the following inputs
URL : localhost:8080/otp
Body :
{
"mobile":123456
}
But I am getting the following exception
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Can not deserialize instance of java.lang.Integer out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.Integer out of START_OBJECT token
If I am taking String as a parameter like this
#PostMapping(value = "/otp")
public void otp(#RequestBody String mobile) {
System.out.println(" Mobile = "+mobile);
}
And passing the inputs as
{
"mobile":123456
}
Now it is printing in the console as follows
Mobile = {
"mobile":"123456"
}
But I want only this value 123456. How to achieve my requirement?
NOTE: I don't want to create any additional POJO class or even I don't want to send the data using query/path parameter.
If you want to send your body like:
{
"mobile":123456
}
You will create another object to receive the value.
But if you only want to accept the integer value without any other object, you will not put json object in request body, but only the integer itself.
Body:
12345
You can use a class as request body:
class Request {
public Integer mobile;
}
and specify the parameter like this:
public void otp(#RequestBody Request mobile) {
...
Create a pojo class like below.
public class Mobile{
private Integer mobile;
//getter and setter
}
And then
public void otp(#RequestBody Mobile mobile)
to print value use
mobile.getMobile();
Converting process with json and #RequestBody is automatically and need you provide a class which contains proper field.If you insist to send data by request body,you could use String to receive json data as String.For example:
public void test(#RequestBody String request){
log.info(request);
}
In this way the request body you received is a String.You need some other tool to help you convert it.Like org.json,you could get more info from here HttpServletRequest get JSON POST data
But the easiest way is creating a new class to receive the data or changing #RequestBody to #RequestParam or #Pathvariable.
If you still want to use json as the request body,maybe you could create a common class A which contain lots of fields like name,phone number,email...Then after you send a request which only contains mobile,you just need to A.getMobile().In this way, even you get 100 request,you still need one POJO(but not recommend)
Just send the number in JSON.
12345
use #RequestBody to receive the int number.
it will work.
I use postman to send it in RAW JSON.
BTW, I am a beginner learning Spring Boot.
if you have org.json.JSONObject
#PostMapping(value = "/otp")
public void otp(#RequestBody String mobile) {
JSONObject obj = new JSONObejct(mobile);
System.out.print(obj.getInt("mobile"));
}

Using Gson for Restlet to convert Post data (Representation) to an Object

I am trying to post a form to a Restlet ServerResource and read it into an object using Gson Restlet Extension.
There's no documentation on how to use it and nothing on StackOverflow.
What is the correct way of using gson restlet extension?
Following is what I have tried so far:
public class CustomerSegment {
private int visitsMin;
private int visitsMax;
// Getters, Setters and constructors
}
public class CampaignsResource extends ServerResource {
#Post
public Representation createCampaign(Representation entity) {
Form form = new Form(entity);
// Using form is the usual way, which works fine
// form: [[visitsMin=3], [visitsMax=6]]
CustomerSegment segment = null;
// Following hasn't worked
GsonConverter converter = new GsonConverter();
try {
segment = converter.toObject(entity, CustomerSegment.class, this);
//segment = null
} catch (IOException e1) {
e1.printStackTrace();
}
GsonRepresentation<CustomerSegment> gson
= new GsonRepresentation<CustomerSegment>(entity, CustomerSegment.class);
try {
segment = gson.getObject();
//NullPointerException
} catch (IOException e) {
e.printStackTrace();
}
return new EmptyRepresentation();
}
}
Form data that is being posted:
In fact, you can leverage the built-in converter support of Restlet without explicitly use the gson converter.
In fact, when you put the GSON extension within the classpath, the converter it contains is automatically registered within the Restlet engine itself. To check that you can simply use these lines when starting your application:
List<ConverterHelper> converters
= Engine.getInstance().getRegisteredConverters();
for (ConverterHelper converterHelper : converters) {
System.out.println("- " + converterHelper);
}
/* This will print this in your case:
- org.restlet.ext.gson.GsonConverter#2085ce5a
- org.restlet.engine.converter.DefaultConverter#30ae8764
- org.restlet.engine.converter.StatusInfoHtmlConverter#123acf34
*/
Then you can rely on beans within signatures of methods in your server resources instead of class Representation, as described below:
public class MyServerResource extends ServerResource {
#Post
public SomeOutputBean handleBean(SomeInputBean input) {
(...)
SomeOutputBean bean = new SomeOutputBean();
bean.setId(10);
bean.setName("some name");
return bean;
}
}
This works in both sides:
Deserialization of the request content into a bean that is provided as parameter of the handling method in the server resource.
Serialization into the response content of the returned bean.
You don't have anything more to do here.
For the client side, you can leverage the same mechanism. It's based on the annotated interfaces. For this, you need to create an interface defining what can be called on the resource. For our previous sample, it would be something like that:
public interface MyResource {
#Post
SomeOutputBean handleBean(SomeInputBean input);
}
Then you can use it with a client resource, as described below:
String url = "http://localhost:8182/test";
ClientResource cr = new ClientResource(url);
MyResource resource = cr.wrap(MyResource.class);
SomeInputBean input = new SomeInputBean();
SomeOutputBean output = resource.handleBean(input);
So in your case, I would refactor your code as described below:
public class CampaignsResource extends ServerResource {
private String getUri() {
Reference resourceRef = getRequest().getResourceRef();
return resourceRef.toString();
}
#Post
public void createCampaign(CustomerSegment segment) {
// Handle segment
(...)
// You can return something if the client expects
// to have something returned
// For creation on POST method, returning a 204 status
// code with a Location header is enough...
getResponse().setLocationRef(getUri() + addedSegmentId);
}
}
You can leverage for example the content type application/json to send data as JSON:
{
visitsMin: 2,
visitsMax: 11
}
If you want to use Gson, you should use this content type instead of the urlencoded one since the tool targets JSON conversion:
Gson is a Java library that can be used to convert Java Objects into
their JSON representation. It can also be used to convert a JSON string
to an equivalent Java object. Gson can work with arbitrary Java objects
including pre-existing objects that you do not have source-code of.
Hope it helps you,
Thierry

Spring Boot REST application with Jackson - how to handle arbitrary/unknown xml structures passed in?

I am working with a Spring Boot REST application. We are using jackson to handle deserialization of XML as well as JSON passed in the request body. An example of an expected request body looks like this:
<formInput><formNum>999999</formNum><documentData>Completely unknown data structure here!</documentData></formInput>
In the documentData element, we will have a structure that is completely arbitrary/unknown on the server side. We don't care about the structure, because we only want to pass the xml that is nested in documentData on to another service.
The POJO that we are trying to map the request body onto looks like this:
#JsonDeserialize(using=FormInputJsonDeserializer.class)
public class FormInput {
private String formNum
private String documentData
public String getFormNum() {
return formNum
}
public void setFormNum(String formNum) {
this.formNum = formNum
}
public String getDocumentData() {
return documentData;
}
public void setDocumentData(String documentData) {
this.documentData = documentData;
}
}
The custom JsonDeserializer that we are trying to write:
public class FormInputJsonDeserializer extends JsonDeserializer<FormInput> {
#Override
public FormInput deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
FormInput formInput = new FormInput();
String fieldName
JsonToken currentToken
while (parser.nextToken() != null) {
currentToken = parser.getCurrentToken()
if (currentToken.equals(JsonToken.END_OBJECT)) {
continue
}
fieldName = parser.getCurrentName()
// formNum handling not written yet
if ("documentData".equalsIgnoreCase(fieldName)) {
if (parser.getCurrentToken().equals(JsonToken.START_OBJECT)) {
// we are at the start of documentData, and we need to capture the
// entire documentData node as a String since we don't know
// its structure
JsonFactory jfactory = new JsonFactory()
StringWriter jsonStringWriter = new StringWriter()
JsonGenerator jGen = jfactory.createGenerator(jsonStringWriter)
jGen.copyCurrentStructure(parser) // points to END_OBJECT after copy
jGen.close()
String documentDataJsonStr = jsonStringWriter.getBuffer().toString()
println("documentDataJsonStr: " + documentDataJsonStr)
}
}
}
// rest of code omitted
}
}
As I say, if the request body is xml, ideally I'd like to just keep it formatted as xml and assign that to the documentData String property. However I came up with the above custom deserialization code by following some other examples on StackOverflow. This parsing code ends up converting documentData to a JSON formatted String. Since I didn't know how to pass through the raw XML and get it mapped to the String property, I thought I could just convert the JSON formatted String back to a XML formatted String. A problem arises when we pass in a XML structure like this:
<formInput><formNum>9322</formNum><documentData><repeatLevel><subForm1><GROSS_DISTR> 13,004.31</GROSS_DISTR><GROSS_DISTR> 13,004.31</GROSS_DISTR><GROSS_DISTR> 13,004.31</GROSS_DISTR></subForm1></repeatLevel><repeatLevel><subForm1><GROSS_DISTR> 38,681.37</GROSS_DISTR><GROSS_DISTR> 38,681.37</GROSS_DISTR><GROSS_DISTR> 38,681.37</GROSS_DISTR></subForm1></repeatLevel></documentData></formInput>
After documentData is parsed in the deserialize method, the println statement shows the parsed JSON String as:
{"repeatLevel":{"subForm1":{"GROSS_DISTR":" 13,004.31","GROSS_DISTR":" 13,004.31","GROSS_DISTR":" 13,004.31"}},"repeatLevel":{"subForm1":{"GROSS_DISTR":" 38,681.37","GROSS_DISTR":" 38,681.37","GROSS_DISTR":" 38,681.37"}}}
This is actually not strictly valid JSON, due to the duplicate keys. I would have hoped that these would have been converted to JSON arrays, but that is not the case. So, I am unable to turn around and use something like the JSON.org libraries (JsonObject and XML) to convert the JSON String back to XML format (get an exception with a "duplicate key" error).
Does anybody have any suggestions or strategies for handling our situation?
You could try to use a JSONObject, add the #JsonIgnoreProperties("documentData") tag and extract documentData seperately from the raw data using substring()

Categories