I'm setting up an api client with retrofit, and so far GETs are working fine, but I'm trying to create a new object with a POST, and instead of the object being sent as json, the request body just contains the string "null":
---> HTTP POST http://myapiurl
Content-Type: application/json; charset=UTF-8
Content-Length: 4
null
---> END HTTP (4-byte body)
Here is the method I'm trying to call:
#POST("/Monsters/")
Response new_monster(#Body Monster mon);
And here is how I'm calling it:
#Test
public void testNew_monster() throws Exception {
//create a new monster object, and pass it to the function,
//then query and verify it's in the results?
Monster newmon = new Monster() {
{
name = "deleteme";
description = "created by junit test testNew_monster";
image_url = "http://i.imgur.com/";
created_by = "";
encoded_key = "";
}
};
Response r = client.new_monster(newmon);
assertEquals(201, r.getStatus());
//sleep a couple seconds here?
List<Monster> monsterList = client.monsters();
assertTrue(monsterList.contains(newmon));
}
I am guessing something is going wrong when the object is serialized to json with GSON, but I'm unable to see anything helpful with the debugger during the serialization process...
I'm using GSON version 2.3.1
EDIT: Here's how I'm building the RestAdapter and the client:
static MonSpottingApi GetClient(boolean dbg)
{
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(API_URL)
.build();
if (dbg) restAdapter.setLogLevel(RestAdapter.LogLevel.FULL);
MonSpottingApi client = restAdapter.create(MonSpottingApi.class);
return client;
}
In the test case class:
MonSightingClient.MonSpottingApi client;
#Before
public void setUp() throws Exception {
client = MonSightingClient.GetClient(true);
}
I suspected that the root cause was Gson, so I started making very simple tests and trying to get objects to correctly serialize using toJson(). I think I found a bug in Gson, where it fails if an object is initialized with double-brace syntax:
Using the example class found here
public class GitHubTest {
//fails
#Test
public void testGson1() throws Exception {
GitHubClient.Contributor contrib = new GitHubClient.Contributor() {
{
login = "someguy";
contributions = 99;
}
};
Gson gson = new Gson();
String json = gson.toJson(contrib);
System.out.println("contents of json string: " + json);
assertNotEquals(json, "null");
}
//passes
#Test
public void testGson2() throws Exception {
GitHubClient.Contributor contrib = new GitHubClient.Contributor();
contrib.contributions = 99;
contrib.login = "someguy";
Gson gson = new Gson();
String json = gson.toJson(contrib);
System.out.println("contents of json string: " + json);
assertNotEquals(json, "null");
}
}
Is this a bug in Gson? Or is there some weird subtle Java reason that this happens? (Java is not my strongest language).
I had the same issue, and it looks like a Gson is not happy with the double-brace initialization of the object that is to be converted.
Replacing the double-brace initialization...
AuthenticationRequest req = new AuthenticationRequest() {{
setName("myName");
setPassword("myPass");
}}
with...
AuthenticationRequest req = new AuthenticationRequest();
req.setUserId("myName");
req.setPassword("myPass");
...did the trick.
You have to pass your interface class to create() method of Restadapter. Let's say your interface class is INewService (where you declared new_monster), then GetClient should look like:
public static INewService GetClient(boolean dbg){
RestAdapter restAdapter = new RestAdapter.Builder().
.setEndpoint(API_URL).
.setClient(new OkClient(new OkHttpClient()))
.build();
if (dbg) restAdapter.setLogLevel(RestAdapter.LogLevel.FULL);
return restAdapter.create(INewService.class);
}
Related
This is my first time using jackson/consuming apis/httpclient. I'm getting this error com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.util.ArrayList<WallHaven> from Object value (token JsonToken.START_OBJECT) . The api I'm trying to consume is https://wallhaven.cc/help/api
try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create("https://wallhaven.cc/api/v1/w/pkgkkp"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
ObjectMapper mapper = new ObjectMapper();
List<WallHaven> posts = mapper.readValue(response.body(), new TypeReference<List<WallHaven>>() {
});
posts.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
}
The api json format is https://pastebin.com/tbSaVJ1T
Here's my WallHaven class
public class WallHaven {
public Data data;
public WallHaven(Data data) {
this.data = data;
}
public WallHaven() {
}
#Override
public String toString() {
return "WallHaven{" +
"data=" + data.getPath() +
'}';
}
}
Data contains all the other classes/variables
This is happening because you're trying to deserialize a Json Object into a List in java. The error message explains it by saying that the starting character (JsonToken.START_OBJECT) is the start of a json object not a json array, so you can't deserialize it directly into a List, but should deserialize it into an object.
Try changing:
List<WallHaven> posts = mapper.readValue(response.body(), new TypeReference<List<WallHaven>>())
into
WallHaven post = mapper.readValue(response.body(), new TypeReference<WallHaven>())
Friends!
I have a simple HTTP request:
void postRequest(String postUrl,String phone, String message) throws IOException {
OkHttpClient client = new OkHttpClient();
//RequestBody body = RequestBody.create(JSON, postBody);
RequestBody body = new FormBody.Builder()
.add("phone", phone)
.add("message", message)
.build();
Request request = new Request.Builder()
.url(postUrl)
.post(body)
.build();
//System.out.println(request);
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
call.cancel();
}
#Override
public void onResponse(Call call, Response response) throws IOException {
Log.d("TAG",response.body().string());
}
});
}
How to properly implement sending a JSON object instead of simple parameters?
My attempts were unsuccessful, so I really need a hint.
The server that will accept JSON is running on AKKA-HTTP.
How do I send a request to this server correctly?
final case class Message(phone: String, message: String, service: String)
implicit val item = jsonFormat3(Message)
val queue: Queue[Message] = Queue()
val addMessage = post {
path("add_message"){
parameters("phone".as[String], "message".as[String], "service".as[String]){
(phone, message, service) => {
queue.enqueue(Message(phone, message, service))
complete("ok")
}
}
}
}
The easiest way to map and serialize your object in JSON format is to use the ObjectMapper class of jackson-databind library.
I personally use it to implement integration tests of RestControllers and it works very well. Here is the utility class I realized, you can take it and use it for your purposes:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public final class JsonUtils {
public static String json(Object obj) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(obj);
}
}
What you need to have is a POJO class which implements Serializable, and then pass the instance of your class to the json method and it will return the JSON format.
You can definitely use it for Android projects. I found many examples where you can add the dependency, but it depends whether you use Gradle or Maven.
Try that out!!!
How do you like this option?
I tried to implement it, but the send fails.
I'm missing an important detail. But I don't understand what it is.
// create your json here
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("KEY1", "VALUE1");
jsonObject.put("KEY2", "VALUE2");
} catch (JSONException e) {
e.printStackTrace();
}
OkHttpClient client = new OkHttpClient();
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
// put your json here
RequestBody body = RequestBody.create(JSON, jsonObject.toString());
Request request = new Request.Builder()
.url("https://YOUR_URL/")
.post(body)
.build();
Response response = null;
try {
response = client.newCall(request).execute();
String resStr = response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
I'm attempting to use Retrofit to call the GitHub API to update the contents of an existing file, but am getting 404s in my responses. For this question, I'm interested in updating this file. Here is the main code I wrote to try and achieve this:
GitHubUpdateFileRequest
public class GitHubUpdateFileRequest {
public String message = "Some commit message";
public String content = "Hello World!!";
public String sha = "shaRetrievedFromSuccessfulGETOperation";
public final Committer committer = new Committer();
private class Committer {
Author author = new Author();
private class Author {
final String name = "blakewilliams1";
final String email = "blake#blakewilliams.org";
}
}
}
**GitHubUpdateFileResponse **
public class GitHubUpdateFileResponse {
public GitHubUpdateFileResponse() {}
}
GitHubClient
public interface GitHubClient {
// Docs: https://docs.github.com/en/rest/reference/repos#get-repository-content
// WORKS FINE
#GET("/repos/blakewilliams1/blakewilliams1.github.io/contents/qr_config.json")
Call<GitHubFile> getConfigFile();
// https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents
// DOES NOT WORK
#PUT("/repos/blakewilliams1/blakewilliams1.github.io/contents/qr_config.json")
Call<GitHubUpdateFileResponse> updateConfigFile(#Body GitHubUpdateFileRequest request);
}
Main Logic
// Set up the Retrofit client and add an authorization interceptor
UserAuthInterceptor interceptor =
new UserAuthInterceptor("blake#blakewilliams.org", "myActualGitHubPassword");
OkHttpClient.Builder httpClient =
new OkHttpClient.Builder().addInterceptor(interceptor);
Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.client(httpClient.build()).build();
client = retrofit.create(GitHubClient.class);
// Now make the request and process the response
GitHubUpdateFileRequest request = new GitHubUpdateFileRequest();
client.updateConfigFile(request).enqueue(new Callback<GitHubUpdateFileResponse>() {
#Override
public void onResponse(Call<GitHubUpdateFileResponse> call, Response<GitHubUpdateFileResponse> response) {
int responseCode = response.code();
// More code on successful update
}
#Override
public void onFailure(Call<GitHubUpdateFileResponse> call, Throwable t) {
Log.e("MainActivity", "Unable to update file" + t.getLocalizedMessage());
}
});
What currently happens:
Currently, the success callback is triggered, but with a response code of 404 like so:
Response{protocol=http/1.1, code=404, message=Not Found, url=https://api.github.com/repos/blakewilliams1/blakewilliams1.github.io/contents/qr_config.json}
Has anyone else encountered this? I first thought it was a problem with including '/content/' in the URL but I do the same thing for reading the file contents request and it works fine (also uses same URL just a GET instead of PUT).
For anyone interested in doing this in the future, I figured out the solution.
I needed to revise the request object structure
Rather than using an authentication interceptor, I instead added an access token to the header. Here is where you can create access tokens for Github, you only need to grant it permissions to the 'repos' options for this use case to work.
This is what my updated request object looks like:
public class GitHubUpdateFileRequest {
public String message;
public String content;
public String sha;
public final Committer committer = new Committer();
public GitHubUpdateFileRequest(String unencodedContent, String message, String sha) {
this.message = message;
this.content = Base64.getEncoder().encodeToString(unencodedContent.getBytes());
this.sha = sha;
}
private static class Committer {
final String name = "yourGithubUsername";
final String email = "email#yourEmailAddressForTheUsername.com";
}
}
Then from my code, I would just say:
GitHubUpdateFileRequest updateRequest = new GitHubUpdateFileRequest("Hello World File Contents", "This is the title of the commit", shaOfExistingFile);
For using this reqest, I updated the Retrofit client implementation like so:
// https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents
#Headers({"Content-Type: application/vnd.github.v3+json"})
#PUT("/repos/yourUserName/yourRepository/subfolder/path/to/specific/file/theFile.txt")
Call<GitHubUpdateFileResponse> updateConfigFile(
#Header("Authorization") String authorization, #Body GitHubUpdateFileRequest request);
And I call that interface like this:
githubClient.updateConfigFile("token yourGeneratedGithubToken", request);
And yes, you do need the prefix "token ". You could hardcode that header into the interface, but I pass it in so that I can store it in locations outside of my version control's reach for security reasons.
I'm facing an issue in new project. I'm connecting to API that could response two different data types in one response based on some server logic. I'm using Retrofit on Android and I was wondering if there's some "easy" way to handle that cases before retrofit object parse, eg. some kind of parser/serializer that would check what type has specific JSON field? I dunno.
Here are possible responses:
error response:
{
"ReturnCode": "error",
"ReturnCodeNumber": 444,
"ReturnMessage": "Invalid Request",
"ReturnData": ""
}
data response:
{
"ReturnCode": "ok",
"ReturnCodeNumber": 0,
"ReturnMessage": "success",
"ReturnData": [
{
}
]
}
Retrofit API request:
#FormUrlEncoded
#POST("url")
Observable<ApiResponse<List<Data>>> requestData()
API response class has exposed fields of above response and parameterized T for returnData.
So is it possible to somehow wrap it in some serializer class?
You will have to write custom deserializer or register a type adapter as explained in this -
https://sites.google.com/site/gson/gson-user-guide#TOC-Custom-Serialization-and-Deserialization
Try to do by using TypeAdapterFactory. Sample of that class as shown below.
public class ItemTypeAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = elementAdapter.read(in);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has("data") && jsonObject.get("data").isJsonObject()) {
jsonElement = jsonObject.get("data");
}
}
return delegate.fromJsonTree(jsonElement);
}
}.nullSafe();
}
}
And this Gson into RestAdapter
final Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ItemTypeAdapterFactory()).create();
final Client client = new OkClient(new OkHttpClient());
final RestAdapter restAdapter = new RestAdapter.Builder().setClient(client).setLogLevel(RestAdapter.LogLevel.FULL).setConverter(new GsonConverter(gson)).setEndpoint(context.getString(R.string.base_url)).build();
In my Android app I connect through REST to Delphi application on Firebird server using Retrofit.
There is REST method "SelectSQL" which takes two parameters: String and TJSONObject.
For example:
select name from employee where employee_id=:id
{"id":10001}
This is part of interface where I declare methods: I used string and JSONObject.
#POST("datasnap/rest/TstBaseMethods/SelectSQL/{param,param2}")
Call<Logowanie> selectSQL(#Header("Authorization") String credentials, #Query("param") String param, #Query("param2") JSONObject param2 );
In my MainActivity.java I use:
Gson gson = new GsonBuilder()
.setLenient()
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
StreamREST gerritAPI = retrofit.create(StreamREST.class);
String dane = Credentials.basic("admin","admin");
JSONObject obj = new JSONObject();
try {
obj.put("NAGL",11101);
String dd = obj.toString();
Call<Logowanie> sql = gerritAPI.selectSQL(dane,"select n.datadok from nagl n where n.id_nagl=:NAGL",obj);
sql.enqueue(new Callback<Logowanie>() {
#Override
public void onResponse(Call<Logowanie> call, Response<Logowanie> response) {
if(response.isSuccessful()) {
Logowanie log = response.body();
String result = log.result[0];
intent.putExtra(EXTRA_MESSAGE,generujWynik(log));
startActivity(intent);
} else {
System.out.println(response.toString());
}
}
#Override
public void onFailure(Call<Logowanie> call, Throwable t) {
System.out.println(t.getMessage() );
}
});
} catch (JSONException e) {
e.printStackTrace();
}
The problem is Retrofit doesn't properly encodes second parameter. When I launch app, I get message about bad second parameter and URL looks like that:
http://192.168.94.155:9000/datasnap/rest/TstBaseMethods/SelectSQL/%7Bparam,param2%7D?param=select%20n.datadok%20from%20nagl%20n%20where%20n.id_nagl%3D:NAGL¶m2={%22NAGL%22:11101}
You can see: "param2={%22NAGL%22:11101}" where param2 looks like:
{"NAGL":11101}
Brackets and "" aren't encoded. Where is my mistake? I admit these are my first steps with Android and REST with Stackoverflow as well, but I've already made basics: I launched basic REST method and it worked. Now is problem for me.
If I didn't include important parts of my code, just tell me and I will do it.
I can add that it's about DataSnap REST from Embarcadero.
Problem solved. It turned out that I had to use #Body Retrofit annotation as this parameter should go in body of the request.