My server responds with JSON object always containing the same structre:
[int id, Error error, T result] where result is a type depending on the JSON method being called.
I'm trying to use GSON for deserialization but it looks like having problems when I use generics.
private <T> T executeRequest(String methodName, Object[] params,
Class<T> resultType) throws HttpException, IOException {
final Gson gson = buildGson();
final URL url = new URL(WEB_URL + methodName);
final ZenfolioRequest request = new ZenfolioRequest();
request.setMethod(methodName);
request.setId(getNextRequestId());
request.setParams(params);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod(POST);
connection.setRequestProperty(USER_AGENT_PROPERTY, AGENT_NAME);
connection.setRequestProperty(CONTENT_TYPE, APPLICATION_JSON);
connection.setRequestProperty(ACCEPT, APPLICATION_JSON);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setChunkedStreamingMode(0);
connection.connect();
final String toSend = gson.toJson(request);
connection.getOutputStream().write(toSend.getBytes());
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
final InputStream in = new BufferedInputStream(
connection.getInputStream());
//String response = readResponseString(in);
final T result = gson.fromJson(new InputStreamReader(in, UTF_8),
resultType);
return result;
} else {
throw new HttpException();
}
}
Example method:
#Override
public ZenfolioResponse<AuthChallenge> getChallenge(String loginName) {
try {
final String methodName = "GetChallenge";
return executeRequest(methodName, new Object[] { loginName },
new ZenfolioResponse<AuthChallenge>().getClass());
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
return null;
}
public class ZenfolioResponse<T>{
#SerializedName("id")
int id;
#SerializedName("error")
Error error;
#SerializedName("result")
T result;
public T getResult() {
return result;
}
public void setResult(T result) {
this.result = result;
}
public Error getError() {
return error;
}
public void setError(Error error) {
this.error = error;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
The problem is the object ZenfolioResponse is incorrectly filled with the Result body. It is not typed, it is an Object instance instead of AuthChallenge type.
Does anybody have any hints on how to tweak this?
Try to use TypeToken from GSON library. It will help you to parse generic type. I found example here:
http://www.studytrails.com/java/json/java-google-json-serializing-classes-with-generic-type.jsp
Related
#Service
public class PokemonManager implements PokemonService {
private HttpResponse<String> getStringHttpResponseByUrl(final String url) {
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.GET().header("accept", "application/json")
.uri(URI.create(url)).build();
HttpResponse<String> httpResponse = null;
try {
httpResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return httpResponse;
}
private <T> T getObjectResponse(T t, String url) {
ObjectMapper objectMapper = new ObjectMapper();
try {
t = objectMapper.readValue(getStringHttpResponseByUrl(url).body(), new TypeReference<>() {
});
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return t;
}
private List<Pokemon> getAllPokemonsAsList() {
final String POSTS_API_URL = "https://pokeapi.co/api/v2/pokemon?limit=10000";
PokeApiResponse pokeApiResponse = new PokeApiResponse();
pokeApiResponse = getObjectResponse(pokeApiResponse, POSTS_API_URL);
System.out.println(pokeApiResponse);
return pokeApiResponse.results;
}
#Override
public List<Pokemon> getAll() {
return getAllPokemonsAsList();
}
I have a code as above. If I do not use generics in the "getObjectResponse" method, the code works fine. However, when I use generics, the type of "t" becomes "LinkedHashMap" instead of "PokeApiResponse", and the code crashes. How can I fix this problem?
Generally you would use it:
objectMapper.readValue("yourJSONHere", PokeApiResponse.class);
If you wanted a Generic T response perhaps this would work
private <T> T getGeneric(Class<T> clazz, String json) throws IOException {
return new ObjectMapper().readValue(json, clazz);
}
Example:
Pokemon charmander = getGeneric(Pokemon.class, "{\n" +
" \"name\": \"charmander\"\n" +
"}");
You are not passing enough information for ObjectMapper to parse the JSON this way. Also there is no need to pass the instance of response, you can use the Class instead. I would also extract json parsing logic to separate method:
public static <T> T jsonToModel(String document, Class<T> type) throws IOException {
return new ObjectMapper().readValue(document, type);
}
private List<Pokemon> getAllPokemonsAsList() {
final String postsApiUrl = "https://pokeapi.co/api/v2/pokemon?limit=10000";
final HttpResponse<String> httpResponse = getStringHttpResponseByUrl(postsApiUrl);
final PokeApiResponse pokeApiResponse = jsonToModel(pokeApiResponse, PokeApiResponse.class);
System.out.println(pokeApiResponse);
return pokeApiResponse.results;
}
for several days I have been trying to implement the upload file in Java-GraphQL. I found this topic: How to upload files with graphql-java? I implemented second solutions.
public class FileUpload {
private String contentType;
private byte[] content;
public FileUpload(String contentType, byte[] content) {
this.contentType = contentType;
this.content = content;
}
public String getContentType() {
return contentType;
}
public byte[] getContent() {
return content;
}
}
public class MyScalars {
public static final GraphQLScalarType FileUpload = new GraphQLScalarType(
"FileUpload",
"A file part in a multipart request",
new Coercing<FileUpload, Void>() {
#Override
public Void serialize(Object dataFetcherResult) {
throw new CoercingSerializeException("Upload is an input-only type");
}
#Override
public FileUpload parseValue(Object input) {
if (input instanceof Part) {
Part part = (Part) input;
try {
String contentType = part.getContentType();
byte[] content = new byte[part.getInputStream().available()];
part.delete();
return new FileUpload(contentType, content);
} catch (IOException e) {
throw new CoercingParseValueException("Couldn't read content of the uploaded file");
}
} else if (null == input) {
return null;
} else {
throw new CoercingParseValueException(
"Expected type " + Part.class.getName() + " but was " + input.getClass().getName());
}
}
#Override
public FileUpload parseLiteral(Object input) {
throw new CoercingParseLiteralException(
"Must use variables to specify Upload values");
}
});
}
public class FileUploadResolver implements GraphQLMutationResolver {
public Boolean uploadFile(FileUpload fileUpload) {
String fileContentType = fileUpload.getContentType();
byte[] fileContent = fileUpload.getContent();
// Do something in order to persist the file :)
return true;
}
}
scalar FileUpload
type Mutation {
uploadFile(fileUpload: FileUpload): Boolean
}
I get this error during compilation:
Caused by: com.coxautodev.graphql.tools.SchemaClassScannerError: Expected a user-defined GraphQL scalar type with name 'FileUpload' but found none!
Have you registered it via RuntimeWiring?
Take a look here: Custom Scalar in Graphql-java
You have to extend GraphQLScalarType in your MyScalars class
I want to parse a JSON object from an endpoint (this one here: https://api.coinmarketcap.com/v1/ticker/bitcoin/) and store the value in a variable at a specific attribute, which in this case is the name.
This the ERROR i get:
java.lang.IllegalStateException: Expected a name but was STRING...
AsyncTask.execute(new Runnable() {
#Override
public void run() {
// All your networking logic
// should be here
try {
String u = "https://api.coinmarketcap.com/v1/ticker/bitcoin";
URL coinMarketCapApi = new URL(u);
HttpsURLConnection myConnection = (HttpsURLConnection) coinMarketCapApi.openConnection();
myConnection.setRequestProperty("User-Agent", "my-rest-app-v0.1");
if (myConnection.getResponseCode() == 200) {
// Success
InputStream responseBody = myConnection.getInputStream();
InputStreamReader responseBodyReader =
new InputStreamReader(responseBody, "UTF-8");
JsonReader jsonReader = new JsonReader(responseBodyReader);
jsonReader.beginArray();
while (jsonReader.hasNext()) {
String key = jsonReader.nextName();
if (key.equals("name")) {
String value = jsonReader.nextName();
break; // Break out of the loop
} else {
jsonReader.skipValue();
}
}
jsonReader.close();
myConnection.disconnect();
} else {
// Error handling code goes here
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
you can convert the InputStream to String and then Create JSONArray from that string. like
StringWriter writer = new StringWriter();
IOUtils.copy(inputStream, writer, encoding);
String theString = writer.toString();
JSONArray jsonarray = new JSONArray(theString);
This way you don't have to manually construct the array.
Use this depandency for JSONArray
https://mvnrepository.com/artifact/org.json/json
You can fix the problem using gson.
https://github.com/google/gson
com.google.gson.stream.JsonReader jsonReader =
new com.google.gson.stream.JsonReader(new InputStreamReader(responseBody));
ArrayList<Coin> coins = new Gson().fromJson(jsonReader, Coin.class);
coins.forEach(coin -> System.out.println(coin.name));
public class Coin{
private String id;
private String name;
private String symbol;
private int rank;
#SerializedName("price_usd")
private double priceUsd;
...........
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getSymbol() {
return symbol;
}
public int getRank() {
return rank;
}
public double getPriceUsd() {
return priceUsd;
}
..........
}
I was using this in another place as async but I wanted to refactor to make it reusable, how can I reorganize the code in order to work as a consumable class?. It doesn't work if it's not async and the ip of the backend is well defined so it's not that. Any ideas?
public class HTTPRequestManager {
public static JSONArray fetchData(){
return null;
}
public static String postData(Context context, String url, String JSONData) {
return null;
}
/* #Override
protected Integer doInBackground(String... strings) {
try {
//1.create client Object
OkHttpClient client = new OkHttpClient();
//2.Define request being sent to server
RequestBody postData = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), JSONData);
Request request = new Request.Builder()
.url(context.getResources().getString(R.string.backend_base_url) + url)
.post(postData)
.build();
//3.Transport the request and wait for response to process next
Response response = client.newCall(request).execute();
String resultData = response.body().string();
if (resultData.equals("OK")) {
} else {
//post failed
return "FAILED";
}
return resultData;
} catch (Exception e) {
Log.d("API_CONNECTION_ERROR", "Couldn't connect to the API");
return "API_CONNECTION_ERROR";
}
}*/
}
I used to have this annonymous class embeeded in another class and it works(it's a get request) but the problem is that it's not reusable in that way:
public class AsyncHttpTask extends AsyncTask<String, Void, Integer> {
URL url = null;
#Override
protected void onPreExecute() {
getActivity().setProgressBarIndeterminateVisibility(true);
}
#Override
protected Integer doInBackground(String... params) {
Integer result = 0;
HttpURLConnection urlConnection;
try {
url = new URL (getResources().getString(R.string.backend_base_url) +
"api/flrcks/user/id/0/latitude/3000/longitude/300/within/9999999999999999999999999");
urlConnection = (HttpURLConnection) url.openConnection();
int statusCode = urlConnection.getResponseCode();
// 200 represents HTTP OK
if (statusCode == 200) {
BufferedReader r = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = r.readLine()) != null) {
response.append(line);
}
parseResult(response.toString());
result = 1; // Successful
} else {
result = 0; //"Failed to fetch data!";
}
} catch (Exception e) {
Log.d(TAG, e.getLocalizedMessage());
}
return result; //"Failed to fetch data!";
}
#Override
protected void onPostExecute(Integer result) {
// Download complete. Let us update UI
progressBar.setVisibility(View.GONE);
if (result == 1) {
adapter = new MyRecyclerAdapter_Nearby(getActivity(), feedsList);
mRecyclerView.setAdapter(adapter);
checkAdapterIsEmpty();
} else {
Toast.makeText(getActivity(), "Failed to fetch data!", Toast.LENGTH_SHORT).show();
t.setVisibility(View.VISIBLE);
}
}
private void parseResult(String result) {
try {
JSONObject response = new JSONObject(result);
JSONArray posts = response.getJSONArray("rows");
feedsList = new ArrayList<>();
JSONArray members;
for (int i = 0; i < posts.length(); i++) {
memberList = new ArrayList<>();
final JSONObject post = posts.optJSONObject(i);
members=post.getJSONArray("members");
final FeedItem item = new FeedItem();
//for (int i = 0; i < posts.length(); i++) {
//JSONObject post = posts.optJSONObject(i);
//FeedItem item = new FeedItem();
item.setId(post.optString("id"));
item.setTitle(post.optString("name"));
item.setDescription(post.optString("description"));
item.setPrivacy(post.optString("privacy_mode_description"));
item.setInitial_date(post.optString("initial_date"));
item.setThumbnail(post.optString("thumbnail"));
item.setColor_hex(post.optString("color_hex"));
item.setTag(post.optString("tag"));
item.setDistance(post.optInt("st_distance"));
//item.setThumbnail(post.optString("thumbnail"));
for(int k=0; k <members.length();k++)
{
MemberItem memberItem = new MemberItem();
JSONObject member = members.optJSONObject(k);
memberItem.setName(member.optString("name"));
memberItem.setUsername(member.optString("username"));
memberItem.setProfile_pic(member.optString("profile_pic"));
memberItem.setIs_moderator(member.optBoolean("is_moderator"));
memberItem.setFacebookId(member.optString("facebook_id"));
memberList.add(memberItem);
}
item.setMemberList(memberList);
feedsList.add(item);
}
}
catch (JSONException e) {
e.printStackTrace();
}
}
}
What changes do I need to make to put it in an isolated file to be consumed by the whole app like for example in a file called OkHTTPRequests.class???
Create an interface
public interface OnWebResponseListener {
void onWebResponse(CommonUtilities.services service, String result);
}
create a public enum for identifying service. in my case i created a CommonUtilities java where i declared
public enum services {
LOGIN
}
Your Common File
public class CallAddr extends AsyncTask<String, Void, String> {
CommonUtilities.services service;
OnWebResponseListener onWebResponseListener;
String url;
FormBody.Builder body;
Request request;
OkHttpClient client;
final static String TAG = "CallAddr";
public CallAddr(Map<String, String> data, CommonUtilities.services service, OnWebResponseListener onWebResponseListener, String url) {
this.service = service;
this.onWebResponseListener = onWebResponseListener;
this.url = url;
body = new FormBody.Builder();
for (String key : data.keySet()) {
body.add(key, data.get(key));
}
client = new OkHttpClient();
}
#Override
protected String doInBackground(String... strings) {
String result = "";
request = new Request.Builder().url(url).post(body.build()).build();
try {
Response response = client.newCall(request).execute();
result = response.body().string();
} catch (Exception e) {
Log.e(TAG,Log.getStackTraceString(e));
}
return result;
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
if (onWebResponseListener != null) {
onWebResponseListener.onWebResponse(service, s);
}
}
}
Here is my Json returned from the server
{"ErrorCode":1005,"Message":"Username does not exist"}
Here is my class for an error
public class ErrorModel {
public int ErrorCode;
public String Message;
}
and here is my conversion code.
public static ErrorModel GetError(String json) {
Gson gson = new Gson();
try
{
ErrorModel err = gson.fromJson(json, ErrorModel.class);
return err;
}
catch(JsonSyntaxException ex)
{
return null;
}
}
It is always throwing a JsonSyntaxException. Any ideas what could be my problem here?
EDIT: As requested, here is further elaboration.
My backend is an ASP.NET MVC 2 application acting as a rest API. The backend isn't the problem here, as my actions (and even server errors) all return Json (using the built in JsonResult). Here's a sample.
[HttpPost]
public JsonResult Authenticate(AuthenticateRequest request)
{
var authResult = mobileService.Authenticate(request.Username, request.Password, request.AdminPassword);
switch (authResult.Result)
{
//logic omitted for clarity
default:
return ExceptionResult(ErrorCode.InvalidCredentials, "Invalid username/password");
break;
}
var user = authResult.User;
string token = SessionHelper.GenerateToken(user.UserId, user.Username);
var result = new AuthenticateResult()
{
Token = token
};
return Json(result, JsonRequestBehavior.DenyGet);
}
The basic logic is to auth the user cretentials and either return an ExceptionModel as json or an AuthenticationResult as json.
Here is my server side Exception Model
public class ExceptionModel
{
public int ErrorCode { get; set; }
public string Message { get; set; }
public ExceptionModel() : this(null)
{
}
public ExceptionModel(Exception exception)
{
ErrorCode = 500;
Message = "An unknown error ocurred";
if (exception != null)
{
if (exception is HttpException)
ErrorCode = ((HttpException)exception).ErrorCode;
Message = exception.Message;
}
}
public ExceptionModel(int errorCode, string message)
{
ErrorCode = errorCode;
Message = message;
}
}
When the above authentication is called with invalid credentials, the error result is returned as expected. The Json returned is the Json above in the question.
On the android side, I first build an object with my key-value pairs.
public static HashMap<String, String> GetAuthenticationModel(String username, String password, String adminPassword, String abbr)
{
HashMap<String, String> request = new HashMap<String, String>();
request.put("SiteAbbreviation", abbr);
request.put("Username", username);
request.put("Password", password);
request.put("AdminPassword", adminPassword);
return request;
}
Then, I send off an http post and return as a string whatever is sent back.
public static String Post(ServiceAction action, Map<String, String> values) throws IOException {
String serviceUrl = GetServiceUrl(action);
URL url = new URL(serviceUrl);
URLConnection connection = url.openConnection();
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setUseCaches(false);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
String data = GetPairsAsString(values);
DataOutputStream output = new DataOutputStream(connection.getOutputStream());
output.writeBytes(data);
output.flush();
output.close();
DataInputStream input = new DataInputStream(connection.getInputStream());
String line;
String result = "";
while (null != ((line = input.readLine())))
{
result += line;
}
input.close ();
return result;
}
private static String GetServiceUrl(ServiceAction action)
{
return "http://192.168.1.5:33333" + action.toString();
}
private static String GetPairsAsString(Map<String, String> values){
String result = "";
Iterator<Entry<String, String>> iter = values.entrySet().iterator();
while(iter.hasNext()){
Map.Entry<String, String> pairs = (Map.Entry<String, String>)iter.next();
result += "&" + pairs.getKey() + "=" + pairs.getValue();
}
//remove the first &
return result.substring(1);
}
Then I take that result and pass it into my parser to see if it is an error
public static ErrorModel GetError(String json) {
Gson gson = new Gson();
try
{
ErrorModel err = gson.fromJson(json, ErrorModel.class);
return err;
}
catch(JsonSyntaxException ex)
{
return null;
}
}
But, JsonSyntaxException is always thrown.
Might help to know more about the exception, but the same code sample works fine here. I suspect there's a piece of code you omitted that's causing the problem (perhaps the creation/retrieval of the JSON string). Here's a code sample that worked fine for me on Java 1.6 and Gson 1.6:
import com.google.gson.Gson;
public class ErrorModel {
public int ErrorCode;
public String Message;
public static void main(String[] args) {
String json = "{\"ErrorCode\":1005,\"Message\":\"Username does not exist\"}";
Gson gson = new Gson();
ErrorModel err = gson.fromJson(json, ErrorModel.class);
System.out.println(err.ErrorCode);
System.out.println(err.Message);
}
}