I want to store the request body into the database. I am storing the headers and request into the audit trail table.
For conversion of headers, I am creating a map and then storing the json into the database like this:
String method = httpServletRequest.getMethod();
Enumeration<String> values = httpServletRequest.getHeaderNames();
Map<String,String> headers = new HashMap<String, String>();
while (values.hasMoreElements()) {
String key = values.nextElement();
String innerValue = (String)httpServletRequest.getHeader(key);
headers.put(key,innerValue);
}
// converting object to Array
auditTrail.setHeaders(appUtility.objectToString(headers));
How can I store the request body as json into the database (ignore files for now)? The request body can be one object, array of objects or a combination.
Right now, I have written an ASPECT which will store all the incoming requests into the database. It's easy to store the json of the object when I know the Object but how can we make it generic?
If you know that content is JSON and want to read data from stream you can do it on this way:
String jsonBody = request.getReader().lines()
.collect(Collectors.joining(System.lineSeparator()));
If you need to validate is jsonBody really JSON you can use Jackson:
public static boolean isJSONValid(String jsonInString ) {
try {
final ObjectMapper mapper = new ObjectMapper();
mapper.readTree(jsonInString);
return true;
} catch (IOException e) {
return false;
}
}
If you have a body as the object you can use Jackson.
Example:
SomeObject body = ...; // Your body
ObjectMapper objectMapper = new ObjectMapper();
String bodyAsString = objectMapper.writeValueAsString(body); // To JSON
body = objectMapper.readValue(bodyAsString, SomeObject.class); // From JSON
Jakson with maven:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
Related
I am trying to get a large json response using a recursive REST call like below:
private List<MyPojo> recursiveRestCallMethod(String folderId) throws IOException {
List<MyPojo> mypojoList = new ArrayList<>();
String hugeJson = webClient.get()
.uri("/my/rest/api/accepting/" + folderId
+ "/and/producing/huge/jsonresponse/for/all/files/recursively")
.retrieve().bodyToMono(String.class).block();
byte[] bytes = hugeJson.getBytes("UTF-8");
String json = new String(bytes, StandardCharsets.UTF_8);
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode node = objectMapper.readValue(json, ObjectNode.class);
objectMapper.configure(
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
if (node.get("list").get("entries").isArray()) {
for (JsonNode jsonNode : node.get("list").get("entries")) {
MyPojo pojo = new MyPojo();
JsonNode mainNode = jsonNode.get("entry");
if (mainNode.get("isFile").asBoolean()) {
JsonNode nameNode = mainNode.get("name");
pojo.setNodename(nameNode.toString());
// and 20 more fields
mypojoList.add(pojo);
}
if (mainNode.get("isFolder").asBoolean()) {
mypojoList.addAll(recursiveRestCallMethod(mainNode.get("id").toString().replaceAll("\"", "").trim()));
}
}
return mypojoList;
}
return null;
}
Now everytime the json returned has 4193150 characters and it throws exception - Unexpected end-of-input: expected close marker for Object as reported here and some other SO threads (obviously, the json is not complete and valid).
The incomplete json I am getting looks something like:
{"list":{"pagination":{"count":6097,"hasMoreItems":false,"totalItems":6097,"skipCount":0,"maxItems":10000},"entries":[{"entry":{"....
From above, as you can see I should get 6097 objects, but I am getting only 2024 entry array items. And after that json ends abruptly. i.e. invalid json string.
However, for smaller response, where I have 20/30 entry array items, it works as expected.
Note: I am using Spring-Boot 2.4.5 and hence Jackson 2.12.4
Question: Even though I am using .block(), why the response stops at 4193150 characters? What I am doing wrong here?
Not sure what was wrong using String but when I switched to DataBuffer, it worked fine.
Here is the snippet for what I used:
final Flux<DataBuffer> hugeJson = webClient.get()
.uri("/my/rest/api/accepting/" + folderId
+ "/and/producing/huge/jsonresponse/for/all/files/recursively")
.accept(MediaType.ALL)
.retrieve()
.bodyToFlux(DataBuffer.class);
Normally to print out request body i using ObjectMapper, but this way removing space and printing object to string in one line, example :
if i send request body like this :
{
"Header" : "value"
}
and i using objectMapper to print that object
ObjectMapper mapper = new ObjectMapper();
mapper.writeValueAsString(requestBody)
the out put is like this :
{"Header":"value"}
how to print Original request body without any modification ?
I'm not sure if you can print it in it's original form but you can pretty print it.
With Jackson's Object Mapper, you can do something like:
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
System.out.println(json);
or
ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
String json = mapper.writeValueAsString(body);
System.out.println(json);
I don't think you can do this without using any framework.
But you can use the gson for this if you enable the prettyPrinting option. Example:
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.create();
prettyPrintedString = gson.toJson(requestBody, Object.class);
I am getting a value in Json Format and Assigned it into a String.
In Java, How can i Convert a String Value into a Data Set of Ignition. And
how do i Convert a Json Object to Data Set?
postRequest.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
ObjectMapper mapper = new ObjectMapper();
HttpEntity entity;
entity = new StringEntity(mapper.writeValueAsString(context), ContentType.APPLICATION_JSON);
postRequest.setEntity(entity);
CloseableHttpResponse httpResponse = null;
httpResponse = httpClient.execute(postRequest);
String response = null;
response = EntityUtils.toString(httpResponse.getEntity());
AnalysisData = response;
Dataset dataset = Dataset.class.cast(vAnalysisData);
Java does not handle Json parsing on it's own. You must import a third party Json library. Google Gson is the one I use. Here is the link. Once the Json response is parsed into Json Elements, then you can add each element to the DataSet.
Share part of your code, so that it will be easier to help.
I know that JSON object is nothing but the String.
My question is that I have a Map of Object and i want to convert it into Json format.
Example :
Java Class ->
Class Person{
private String name;
private String password;
private int number;
}
Java list ->
Map<List<Long>,List<Person>> map=new HashMap<List<Long>,List<Person>>();
..and map has Some data filled in it.
I want to convert that list into
Json Format?
How I can achieve it? Because i want to send it over HttpClient...
If not what is the other alternative way?
As per my knowledge there is Gson API available, but I dont know how to use it and or in other efficient way.
Thank you
Not sure what the problem with Gson is. From the doc:
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
and
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
That object is (as the name suggests) made up of primitives. However Gson will trivially handle objects, collections of objects etc. Life gets a little more complex when using generics etc., but for your example above I would expect Gson to work with little trouble.
Using Gson to convert to Json using Gson at client side.
Sending String array.
String[] subscriberArray = new String[]{"eee", "bbb"};
Gson gson = new Gson();
String recipientInfoStringFormat = gson.toJson(subscriberArray);
Sending Array of User Defined Type.
RecipientInfo[] recipientInfos = new RecipientInfo[1];
RecipientInfo ri = new RecipientInfo();
ri.setA(1);
ri.setB("ss");
recipientInfos.add(ri);
Gson gson = new Gson();
String recipientInfoStringFormat = gson.toJson(recipientInfos);
Using Gson at Server Side to read Data.
For Primitive Types.
String subscriberArrayParam = req.getParameter("subscriberArrayParam");
Gson gson = new Gson();
String[] subscriberArray = gson.fromJson(subscriberArrayParam, String[].class);
for (String str : subscriberArray) {
System.out.println("qq :"+str);
}
For User Defined Object
String recipientInfos = req.getParameter("recipientInfoStringFormat");
Gson gson = new Gson();
RecipientInfo[] ri = gson.fromJson(recipientInfos, RecipientInfo[].class);
You can use jackson also.
Person person= new Person();
ObjectMapper mapper = new ObjectMapper();
try {
// convert personobject to json string, and save to a file
mapper.writeValue(new File("c:\\person.json"), person);
// display to console
System.out.println(mapper.writeValueAsString(person));
} catch (Exception e) {
e.printStackTrace();
}
and vice versa
ObjectMapper mapper = new ObjectMapper();
try {
// read from file, convert it to user class
Person person= mapper.readValue(new File("c:\\person.json"), Person.class);
// display to console
System.out.println(person);
} catch (Exception e) {
e.printStackTrace();
}
for using jackson add this dependency to your POM.xml
<repositories>
<repository>
<id>codehaus</id>
<url>http://repository.codehaus.org/org/codehaus</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.8.5</version>
</dependency>
</dependencies>
I got my Answer but Thank you for Your Responses.
Map<Long,List<Person>> map=new HashMap<Long,List<Person>>();
//adding some data
Gson gson=new Gson();
String mapJsonStr=gson.toJson(map);
//mapJsonStr : is my map's JSon Strin
for reverse
TypeToken<Map<Long,List<Person>>> token = new TypeToken<Map<Long,List<Person>>>(){};
Map<Long,List<Person>> map_new=new HashMap<Long,List<Person>>();
map_new=gson.fromJson(mapJsonStr,token.getType());
//origian map
// map_new is my Map get from map's Json String
That's It.Thank you
I have been doing some work with apache CXF(version 2.2.2) JAX-RS. I am trying to introduce data validation layer in CXF request handler before business method be invoked. Fortunately :), I am encountering an issue on input parameter handling in request handler(DataValidationHandler). I can read the JSON Object manually by following code lines in request handler. But it's duplicated with JSONProvider registered in CXF framework. Because JSON object input stream only can be read once, otherwise we will meet exception "java.io.EOFException: No content to map to Object due to end of input". Moreover, duplicated JSON object deserializing will impacts performance. Following code is sample for your reference.
Read JSON Object from HTTP body manually:
OperationResourceInfo ori = paramMessage.getExchange().get(OperationResourceInfo.class);
MultivaluedMap<String, String> values = new MetadataMap<String, String>();
List<Object> objList = JAXRSUtils.processParameters(ori, values, paramMessage);
Register JSONProvider in CXF JAX-RS framework:
<bean id="JSONProvider" class="com.accela.govxml2.jaxrs.util.JSONProvider"></bean>
Read JSON Object to Java Object from input stream:
public Object readFrom(......){
ObjectMapper objectMapper = new ObjectMapper();
Object result = objectMapper.readValue(entityStream, TypeFactory.defaultInstance().constructType(genericType));
Return result;
}
I am dealing with Path Parameter manually by following code lines.
OperationResourceInfo ori = paramMessage.getExchange().get(OperationResourceInfo.class);
URITemplate t1 = ori.getClassResourceInfo().getURITemplate();
URITemplate t2 = ori.getURITemplate();
UriInfo uriInfo = new UriInfoImpl(paramMessage, null);
MultivaluedMap<String, String> map = new MetadataMap<String, String>();
t1.match(uriInfo.getPath(), map);
String str = map.get(URITemplate.FINAL_MATCH_GROUP).get(0);
t2.match(str, map);
String pathParameter= null;
if (map.containsKey("pathParam") && !ValidationUtil.isEmpty(map.get("pathParam")))
{
pathParameter= map.get("pathParam").get(0);
}
My questions are here:
How to deal with POST/PUT input parameter of http body in request handler in general?
How to avoid performance issue to read input parameter efficiently?
Is there any way to inject the validation (handler/interceptor) layer between parameter reading by CXF(JSONProvider) and business method invoking?
Is there any elegant way to deal with path parameter?
Thanks for your help. Any comments & suggestions will be appreciated.
Regards,
Dylan
I have found another way to inject DataValidation Interceptor into reading parameter phase. We can reuse deserialized input model from message content, which be deserialized by JSONProvider registered in framework. It can improve performance, because only deserialize input model once.
public class DataValidationInInterceptor extends AbstractPhaseInterceptor<Message>
{
public DataValidationInInterceptor()
{
super(Phase.READ);
}
#Override
public void handleMessage(Message message)
{
OperationResourceInfo ori = message.getExchange().get(OperationResourceInfo.class);
Method method = ori.getMethodToInvoke();
Class<?>[] types = method.getParameterTypes();
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (int i = 0; i < types.length; i++)
{
Class<?> type = types[i];
List obj = (List) message.getContent(List.class);
System.out.println(obj);
System.out.println(type);
}
}
}
After researching, I can read the input stream twice based on the following question's answer ( Read stream twice ).
However, JSON object deserializing performance is still my concern. Who has better solution for it?
Intercept request and change message content from CoyoteInputStream to ByteArrayInputStream, so I can read the InputStream twice.
InputStream in = message.getContent(InputStream.class);
if (in != null)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IOUtils.copy(in, baos);
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
message.setContent(InputStream.class, bais);
}
Reset ByteArrayInputStream before Read JSON Object to Java Object from input stream:
public Object readFrom(......){
ObjectMapper objectMapper = new ObjectMapper();
if (entityStream.markSupported())
{
entityStream.reset();
}
Object result = objectMapper.readValue(entityStream, TypeFactory.defaultInstance().constructType(genericType));
return result;
}