New to API's in Java. I'm trying to get a response from an API and print some of it.
My response in the code below prints the whole dictionary, but I want to print just part of it. In the current way, I have no idea how I can get access to the dictionary, as my response is a String and I couldn't find a relevant BodyHandlers method.
How can I do that? Thanks a lot.
This is my code:
HttpRequest urlAnalysisRequest = HttpRequest.newBuilder()
.uri(URI.create("https://www.virustotal.com/api/v3/analyses/....(I put the id here)"))
.header("Accept", "application/json")
.header("x-apikey", "....(I put api key here)")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> urlAnalysisResponse;
try {
urlAnalysisResponse = HttpClient.newHttpClient().send(urlAnalysisRequest, HttpResponse.BodyHandlers.ofString());
System.out.println(urlAnalysisResponse.body());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
This is my response. I'm trying to get "stats":
{
"meta": {
"url_info": {
"url": "http://www.facebook.com/",
"id": "114fb86b9b4e868f8bac2249eb5c444b545f0240c3dadd23312a0bc1622b5488"
}
},
"data": {
"attributes": {
"date": 1641238171,
"status": "completed",
"stats": {
"harmless": 84,
"malicious": 0,
"suspicious": 0,
"undetected": 9,
"timeout": 0
},
..........
The response is written in a format known as JSON. You need a library to parse this. There are a few options; I strongly suggest you go with Jackson.
You can choose to refer to it with string paths, or, you can make a java class that 'matches' this output, e.g:
class VirusTotalResponse {
VTMeta meta;
VTData data;
}
class VTMeta {
VTUrlInfo urlInfo;
}
class VTUrlInfo {
String url;
String id;
}
and so on. With all those classes in place, turn them all into proper classes (use your IDE's various options, or use Project Lombok) and then just ask Jackson to turn that response into an instance of VirusTotalResponse and you'll have a nice shiny java object, you can then just:
int harmless = response.getData().getAttributes().getStats().getHarmless();
You can use ObjectMapper which is library for handling JSON data easily. You can use not only Class for mapping, but also Map.
I recommend this tutorial site.
https://www.baeldung.com/jackson-object-mapper-tutorial
Related
As I'm only interested in checking some of the values from the response for my Contract Test, I want to leave out those fields that are not important to me. Thing is that when I run the test, generated test file checks if body defined in the test.groovy file is identical to the one generated from controller. So, my question is if there's a way to just assert those values that I'm interested in.
test.groovy
Contract.make {
request {
method 'GET'
urlPath('/api/node') {
queryParameters {
parameter('number', '123')
parameter('size', '1')
parameter('status', 'ACTIVE')
}
}
headers {
header("key", "someKey")
}
}
response {
status 200
headers {
contentType applicationJson()
}
body("""
"content":[
{
"id": "123456",
"status":"ACTIVE",
"details":[
{
"object":{
"id":"ccId",
"codeOne":"1",
"codeTwo":"2",
"codeThree":"3",
"_links":{
"self":{
"href":"https://url.com"
},
"style":{
"href":"https://universal-url.com"
},
"universalStyle":{
"href":"https://universal-style-v1-url.com"
}
}
}
"code":null
}
]
This is the error I'm getting
org.junit.ComparisonFailure: expected:<"[content]"> but was:
<"[{"links":[
{"rel":"first","href":"http://localhost/api/node?number=123&status=ACTIVE&page=0&size=1"},
{"rel":"prev","href":"http://localhost/api/node?number=123&status=ACTIVE&page=0&size=1"},
{"rel":"self","href":"http://localhost/api/node?number=123&size=1&status=ACTIVE"},
{"rel":"last","href":"http://localhost/api/node?number=123&status=ACTIVE&page=1&size=1"}],
"content":[{"id":"123456",...
In this case, I don't want to verify any related to "links" array. Is there any configuration I can use in order to bypass it?
Just don't put them in the response and they won't get generated.
I have the following JSON format
{
"file": {
"version": "v1.4",
"release": "1.1"
},
"status": "ON",
"document": {
"status": "NOT_FOUND",
"release": "undefined"
}
}
and I would like to know how I can add the format into my PactDslJsonBody, something like?
DslPart result = new PactDslJsonBody()
.stringType("file.version", "v1.4")
.stringType("file.release", "1.1")
.stringType("status", "ON")
.stringType("document.status", "NOT_FOUND")
.stringType("document.release", "release")
.asBody();
Or is it possible to add a Java Pojo? I have the class ApplicationResponse:
public class ApplicationResponse {
private File file;
private String status;
private Document document;
//...
}
Something like ??
DslPart result = new PactDslJsonBody()
.object(ApplicationResponse)
.asBody();
What could be the best approach? could you please add an example
We attempted to do what you are trying to do using reflection to stub out our pojos. However, our classes carry many Lombok annotations & we couldn't get default values out from builder annotated fields. We gave up trying to use it. But a dev with more time could achieve this no doubt.
I am now actively creating Pacts for our projects and use both LambdaDsl and PactDslJsonBodyto build the interaction.
DslPart actualPactDsl = LambdaDsl.newJsonBody((body) -> {
body
.stringType("status", "ON")
.object("document", (doc) -> {
doc.stringType("status", "NOT_FOUND");
doc.stringType("release", "undefined");
})
.object("file", (file) -> {
file.stringType("version", "v1.4");
file.stringType("release", "1.1");
});
}).build();
or
String pactDslJson = new PactDslJsonBody()
.stringType("status", "ON")
.object("document")
.stringType("status", "NOT_FOUND")
.stringType("release", "undefined")
.closeObject()
.object("file")
.stringType("version", "v1.4")
.stringType("release", "1.1")
.closeObject()
.getBody().toString();
Both of these examples will produce the Json string from your example.
The examples that are a part of Pact-Jvm are really helpful to get your head around the different types of tests you can create.
So I've got a Ninja endpoint here:
public Result processRecurring(Context context, RecurOrderJSON recurOrderJSON) {
String id = recurOrderJSON.id;
String event_type = recurOrderJSON.event_type;
String request_id = recurOrderJSON.request_id;
//Map data = recurOrderJSON.data;
//recurringRouter(event_type, data);
log.info("ID value");
log.info(id);
return JsonResponse.build()
.message("OK")
.toResult();
}
The class I am trying to map to:
public class RecurOrderJSON {
public String id;
public String event_type;
public String request_id;
// Maybe switch data type?
//public Map data;
}
And the route:
router.POST().route("/recurring").with(RecurringController::processRecurring);
I am just trying to send some simple JSON to a webhook and for some reason the object mapping doesn't seem to be working. I think maybe I am misunderstanding the documentation?
http://www.ninjaframework.org/documentation/working_with_json_jsonp.html
The example they give you is this:
If you send that JSON to your application via the HTTP body you only need to add the POJO class to the controller method and Ninja will parse the incoming JSON for you:
package controllers;
public class ApplicationController {
public Result parsePerson(Person person) {
String nameOfPerson = person.name; // will be John Johnson
...
}
}
As far as I can tell, I am doing this correctly? Am I understanding the documentation wrong? Here's an example JSON object - currently I am only trying to grab the top level strings, but I'll eventually want to grab data as well:
{
"id": "hook-XXXXX",
"event_type": "tx-pending",
"data": {
"button_id": "static",
"publisher_organization": "org-XXXXXXX",
"campaign_id": "camp-097714a40aaf8965",
"currency": "USD",
"order_currency": "USD",
"id": "tx-XXXXXXX",
"category": "new-user-order",
"modified_date": "2018-10-15T05:41:12.577Z",
"order_total": 9680,
"button_order_id": "btnorder-77c9e56fd990f127",
"publisher_customer_id": "XymEz8GO2M",
"rate_card_id": "ratecard-41480b2a6b1196a7",
"advertising_id": null,
"event_date": "2018-10-15T05:41:06Z",
"status": "pending",
"pub_ref": null,
"account_id": "acc-4b17f5a014d0de1a",
"btn_ref": "srctok-0adf9e958510b3f1",
"order_id": null,
"posting_rule_id": null,
"order_line_items": [
{
"identifier": "Antique Trading Card",
"description": "Includes Lifetime Warranty",
"amount": 9680,
"publisher_commission": 968,
"attributes": {},
"total": 9680,
"quantity": 1
}
],
"order_click_channel": "webview",
"order_purchase_date": null,
"validated_date": null,
"amount": 968,
"customer_order_id": null,
"created_date": "2018-10-15T05:41:12.577Z",
"commerce_organization": "org-XXXXXX"
},
"request_id": "attempt-XXXXXXX"
}
Currently I am just trying to get the string values, yet I am constantly getting a 500 error and no other indication in my logs of any error.
As far as I can tell, Ninja should just automatically map the JSON to my object, correct?
I successfully reproduced your issue, and then fixed it.
First, for easy way to try/test, I recommend (temporary) modifications:
package controllers;
import models.RecurOrderJSON;
import ninja.Context;
import ninja.Result;
public class RecurringController {
public Result processRecurring(Context context, RecurOrderJSON recurOrderJSON) {
log.info("recurOrderJSON => " + recurOrderJSON);
return ninja.Results.ok();
}
}
And then, update your model this way:
package models;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class RecurOrderJSON {
public String id;
public String event_type;
public String request_id;
public Map data;
#Override
public String toString() {
return "RecurOrderJSON [id=" + id + ", event_type=" + event_type + ", request_id=" + request_id + ", data="
+ data.toString() + "]";
}
}
You can notice:
The data type must stay raw (generic can't be used here)
the important #JsonIgnoreProperties(ignoreUnknown = true) annotation to avoid deserialize issue, if ever your source data does not perfectly match your model (be sure to use the recent version of annotation, in fasterxml sub-package, instead of the old one, in codehaus sub-package)
the toString() implementation only allowing quick check of OK/KO deserialization
Then you can easily test the system with wget, or curl:
curl -H 'Content-Type: application/json' -d "#/tmp/jsonINput.json" -X POST http://localhost:8080/recurring
Notice it is very important to specify the Content-type for good interpretation.
With the /tmp/jsonINput.json file containing exactly the json contents you specified in your question.
This way, everything is working like a charm, obtaining this output:
recurOrderJSON => RecurOrderJSON [id=hook-XXXXX, event_type=tx-pending, request_id=attempt-XXXXXXX, data={button_id=static, publisher_organization=org-XXXXXXX, campaign_id=camp-097714a40aaf8965, currency=USD, order_currency=USD, id=tx-XXXXXXX, category=new-user-order, modified_date=2018-10-15T05:41:12.577Z, order_total=9680, button_order_id=btnorder-77c9e56fd990f127, publisher_customer_id=XymEz8GO2M, rate_card_id=ratecard-41480b2a6b1196a7, advertising_id=null, event_date=2018-10-15T05:41:06Z, status=pending, pub_ref=null, account_id=acc-4b17f5a014d0de1a, btn_ref=srctok-0adf9e958510b3f1, order_id=null, posting_rule_id=null, order_line_items=[{identifier=Antique Trading Card, description=Includes Lifetime Warranty, amount=9680, publisher_commission=968, attributes={}, total=9680, quantity=1}], order_click_channel=webview, order_purchase_date=null, validated_date=null, amount=968, customer_order_id=null, created_date=2018-10-15T05:41:12.577Z, commerce_organization=org-XXXXXX}]
Given the specific input code with data field commented out
//public Map data;
and the posted input JSON that includes this field, the request should fail with 400 Bad Request.
The reason being that Ninja uses Jackson for JSON parsing and it will throw on unknown fields by default.
The quick workaround is to add #JsonIgnoreProperties annotation to RecurOrderJSON class.
e.g.
#JsonIgnoreProperties(ignoreUnknown = true)
public class RecurOrderJSON {
...
}
See: Ignoring new fields on JSON objects using Jackson
Now if the error was not 400 there isn't much information to go by as there doesn't seem to be anything else obviously wrong with the code.
Either post an SSCCE demonstrating the problem or attempt to debug by surfacing the error page with the following method:
Launch the application in debug mode with mvn package ninja:run
Access the end-point with a tool that allows to inspect the response in detail such as curl e.g.
Store request JSON in input.json
Run curl -v -o result.html -H 'Content-Type: application/json' --data '#input.json' http://localhost:8080/recurring
Open result.html to examine the response
Might it be that you are performing a bad request (hence the JSON is not found) but for some Ninja bug it returns error 500?
For example you can take a look here where is stated that parsing an empty JSON in a JSON request does leads to a misguiding error (500) while it is supposed to return 400 "Bad Request"
Context not needed in processRecurring and use Results.json() and return original
public Result processRecurring(RecurOrderJSON recurOrderJSON) {
String id = recurOrderJSON.id;
String event_type = recurOrderJSON.event_type;
String request_id = recurOrderJSON.request_id;
//Map data = recurOrderJSON.data;
//recurringRouter(event_type, data);
log.info("ID value");
log.info(id);
return Results.json().render(recurOrderJSON);
}
Make sure you get the namespace in your RecurOrderJSON
package models;
public class RecurOrderJSON {
public String id;
public String event_type;
public String request_id;
// Maybe switch data type?
//public Map data;
}
Good luck!
Hi I'm trying to send a PUT request using Retrofit that uses $addToSet to my Mlab Server. I can do this using Postman but I'm having trouble doing it using Retrofit.
The collection looks like:
[
{
"_id": {
"$oid": "5abe74bac2ef1603f4045686"
},
"email": "test#gmail.com",
"completedWalks": [
"South Leinster Way"
],
"favWalks": []
}
]
The post man request has the API key, Query, and then $addToSet is passed in the body as so.
And the response is:
I'm trying to do it like this in android.
Retrofit:
#PUT("databases/walks/collections/user")
Call<Update> addCompleted (#Query("apiKey") String apiKey,#Query("q") String Email, #Body Update Query);
My model:
public class Update {
#SerializedName("n")
private String n;
public String getN() {
return n;
}
public Update(String n) {
this.n = n;
}
}
Creating the update object:
String updateComplete = String.format("'$addToSet': {'completedWalks': '%s'}} ", TrailName);
final String query =String.format("{'email': '%s'}",email) ;
final Update queryComplete = new Update(updateComplete);
And the Request:
Call<Update> completeCall = apiService.addCompleted(mlabAPi, query, queryComplete);
completeCall.enqueue(new Callback<Update>() {
#Override
public void onResponse(Call<Update> call, Response<Update> response) {
Toast.makeText(getApplicationContext(),"Walk marked as Complete", Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call<Update> call, Throwable t) {
Log.e(TAG, t.getMessage());
}
});
But this only overwrites whats in the collection and I have:
[
{
"_id": {
"$oid": "5abe74bac2ef1603f4045686"
},
"n": "'$addToSet': {'completedWalks': 'Kerry Head Cycleway'}} "
}
]
Does anyone know where I'm going wrong, should I not be passing $addToSet as a model because it seems to be overwriting all, how do I pass it then?
Thank You.
#Body Update Query -- Retrofit will encode the object passed to this as JSON (assuming you are using the Gson converter, which it appears you are). That is where this "n": "'$addToSet': {'completedWalks': 'Kerry Head Cycleway'}} " is coming from. You need to structure you Java Object the same as your JSON object for gson to serialize it correctly.
I am not familiar with the mlab api, but from your postman, it looks like you want a request body something like this --
public class UpdateRequest {
#SerializedName("$addToSet")
Map<String, String> addToSet = new HashMap();
}
Update your interface to send this object as the body --
#PUT("databases/walks/collections/user")
Call<Update> addCompleted (#Query("apiKey") String apiKey,#Query("q") String Email, #Body UpdateRequest Query);
And create the request body --
UpdateRequest requestBody = new UpdateRequest();
requestBody.addToSet.put("completedWalks", Trailname);
and create the call --
Call<Update> completeCall = apiService.addCompleted(mlabAPi, query, requestBody);
For further debugging, you can see what is actually being sent in your logcat by adding HttpLoggingInterceptor to your retrofit instance.
See here for setup. Then you can compare what your app is sending vs postman and see where things might be going sideways.
Here is my code:
try {
JSONObject json = (JSONObject) new JSONTokener(result).nextValue();
System.out.println(json);
JSONObject json2 = json.getJSONObject("data");
String test = json2.getString("headline");
System.out.println(test);
} catch (JSONException e) {
e.printStackTrace();
}
My String values start with the object data. So I am trying to get that object first and then capture the the object headline inside that.
My problem is, it is not taking the object data from the string.
Once I reach the line JSONObject json2 = json.getJSONObject("data");, it throws the exception. Please shed some light on this.
"data": [
{
"headline": "Close Update"
"docSource": "MIDNIGHTTRADER",
"source": "MTClosing",
"dateTime": "2015-10-23T16:42:46-05:00",
"link": "Markets/News",
"docKey": "1413-A1067083-1B14K77PVTUM1O7PCAFMI3SJO4",
},
The value for the key data is a JSON array containing one object, and not an object itself.
To get that object inside data, replace your line that throws an exception with the following:
JSONObject json2 = json.getJSONArray("data").get(0);
This gets the data array as a JSONArray object and then gets the 0th element, which is the object you want.
Your data "object", isn't actually an object, it's an array, notice the opening square bracket... I'm assuming in your actual code, it closes with one too.
"data": [{
"headline": "Close Update"
"docSource": "MIDNIGHTTRADER",
"source": "MTClosing",
"dateTime": "2015-10-23T16:42:46-05:00",
"link": "Markets/News",
"docKey": "1413-A1067083-1B14K77PVTUM1O7PCAFMI3SJO4",
}]
Try json.getJSONArray("data")[0] instead... or whatever index you need
try {
JSONObject json = (JSONObject) new JSONTokener(result).nextValue();
System.out.println(json);
JSONObject json2 = json.getJSONArray("data")[0];
String test = json2.getString("headline");
System.out.println(test);
}
catch (JSONException e) {
e.printStackTrace();
Your problem is based on the fact that your service returns and array instead of a single json object, so from here you can follow this suggestions to process directly from the JSONArray Can't access getJSONArray in java, or, at server side you can encapsulate your response array into another object like this (java example):
public class Data<T> {
private List<T> elements;
public ObjectSugetionsDTO(){
And build the response like this:
return new ResponseEntity<Data<YourInternalRepresentation>>(
new Data<YourInternalRepresentation>(yourMethodCallForTheArray()),
HttpStatus.OK);
I have found the second way to be better at keeping my API cleaner and more readable
EDIT: Better way
I whould also suggest the use of retrofit (http://square.github.io/retrofit/), by doing so, your service calls is resumed to (Example of calling and API that retrieves a list of users):
public class UserService {
public static IUserService getUserService() {
return RestAdapterManager.createService(IUserService.class );
}
public interface IUserService{
#GET("/api/users")
public void getAllUsers(Callback<List<User>> callback);
}
}
and the service call itself
UserService.getUserService().getAllUsers(new Callback<List<User>>() {
#Override
public void success(List<User> users, Response response) {
Log.d("Exito! " , "" + users.size());
}
#Override
public void failure(RetrofitError error) {
Log.d("Fail!", error.getUrl());
}
});
The simple inicialization of the connection object
public static <S> S createService(Class<S> serviceClass, String username, String password) {
RestAdapter.Builder builder = new RestAdapter.Builder()
.setEndpoint(API_BASE_URL);//Your api base url
RestAdapter adapter = builder.setLogLevel(RestAdapter.LogLevel.FULL).build(); //change the logging level if you need to, full is TOO verbose
return adapter.create(serviceClass);
}