In my current setup I have a centralized helper class like the following:
#Override
protected void onPostExecute(String result)
{
super.onPostExecute(result);
if(task == null) {
return;
}
Gson gson = new GsonBuilder().create();
ResponseJson p = null;
try {
p = gson.fromJson(result, ResponseJson.class);
} catch(JsonSyntaxException e) {
//Toast.makeText(getApplicationContext(), getApplicationContext().getResources().getString(R.string.toast_sync_completed), Toast.LENGTH_SHORT).show();
Log.e("", e.getMessage() + " got '" + result + "'");
}
Log.i("", p.toString());
Log.i("", result);
if(p.status.equals(ResultStatus.SUCCESS.toString())) {
task.success(p.data);
} else if (p.status.equals(ResultStatus.FAIL.toString())) {
task.fail(p.data);
} else if (p.status.equals(ResultStatus.ERROR.toString())) {
task.error(p.message);
} else {
throw new UnsupportedOperationException();
}
}
the problem is i want to make the ResponseJson class to be dynamic and the data attribute to be unique (sometimes i need ArrayList<Something> other times HashMap<String, String>) works for converting my JSON result. can i achieve this using generics or some other means?
If you set Object as type of data,
class ResponseJson {
Object data;
}
Later you can get the results as ArrayList or Hashmap as follows;
if (responseJson.data instanceof ArrayList<?>) {
ArrayList<String> arrayList = (ArrayList<String>) responseJson.data;
} else if(responseJson.data instanceof HashMap<?, ?>) {
HashMap<String, String> hashmap = (HashMap<String, String>) responseJson.data;
}
Due to type erasure, the parameterised type cannot be known at runtime. But if you know the type you can do an unchecked cast.
Related
I don't know if it's clear for you, I want to implement a method like SpringMVC's #RequestBody(), this method can deserialize input json string to object, and this method should support complex object type(like List<List<Object>>)
Now using Java reflection and Jackson, we can easily deserialize List<Object>, such as "[1,100]" to List<Long>, but if the input is "[[1,100]]", there is nested list in another list.
Here is my code:
// there is a class and method's parameter has annotation #Param
class Code{
public void getName(#Param("fooList") List<List<Long>> fooList) {
System.out.println("getName run");
}
}
// reflection Code#getName and deserialize method
public Object inject(Parameter param) {
Object object = null;
try {
Class<?> paramType = param.getType();
Param paramAnnotation = param.getAnnotation(Param.class);
// for unit test purpose, hard code json string here
object = "[[1,3]]";
if (object != null) {
if (Collection.class == paramType || List.class.isAssignableFrom(paramType)) {
// basic type
if (clazz != null) {
// todo: string to list, need support complex List
try {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(text,
objectMapper.getTypeFactory().constructCollectionType(List.class,
clazz));
} catch (Exception e) {
throw new SDKException("");
}
List list = JSONs.toList(object.toString(), clazz);
List converterArgs = new ArrayList<>(list.size());
for (Object arg : list) {
try {
arg = Casts.cast(clazz, arg);
} catch (Exception e) {
throw new InvalidParametersException("type error");
}
converterArgs.add(arg);
}
object = converterArgs;
}
}
if (String.class.isAssignableFrom(paramType)) {
object = JSONs.string(object);
} else {
if (!object.getClass().equals(paramType)) {
try {
object = Casts.cast(paramType, object);
} catch (Exception e) {
throw new InvalidParametersException("type error");
}
}
}
}
} catch (Exception e) {
throw new SDKException("", e);
}
return object;
}
#Test
public void testReflection() {
try {
Method getName = Code.class.getMethod("getName", List.class);
Parameter[] parameters = getName.getParameters();
AnnotationParamInjector injector = new AnnotationParamInjector();
Object inject = injector.inject(parameters[0]);
System.out.println("inject = " + inject);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
Backgroud: I want to implement a method like SpringMVC's controller, so I need to deserialize a method parameter to an object. Right now there is only support for a normal class and a List but none for a complex List.
Questions:
Java reflection can parse generic class, even complex List, we can get reflect List from outer to inner, but how can I parse say a json string "[[1, 100]]"? This is the problem that I'm trying to solve.
Here is my pseudocode, I recursively call constructClass() to get its generic row type, after calling constructClass(), the json string should be parsed, ie something like "[[1,100]]" should change to "[1,100]"
public void constructClass(String text, Class<?> clazz) {
Type type = clazz.getGenericSuperclass();
if (Collection.class == clazz || List.class.isAssignableFrom(clazz)) {
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
Type genericType = pType.getActualTypeArguments()[0];
this.constructClass(text, genericType.getClass());
}
} else if (Map.class == clazz) {
} else {
}
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
pType.getActualTypeArguments()[0]
}
}
Could someone provide a good way to do this?
I have a string like this
{"key0":"value0","key1":"value1","key0":"value3"}
I want to store it in a map and the desired result is {"key0":"value3","key1":"value1"}
Using org.json.JsonObject: I passed the string to the constructor and Duplicate key exception is thrown
Using GSON: Same exception when I tried through new Gson.fromJson(string,Type)
Using Jackson: It does work
Is there a workaround to achieve the same using JSONObject and Gson
Interestingly if you first cast that json to an Object and then to a Map<String,String> your desired result happens:
String json = "{\"key0\":\"value0\",\"key1\":\"value1\",\"key0\":\"value3\"}";
Gson gson = new Gson();
Object obj = gson.fromJson(json, Object.class);
try {
Map<String,String> map = (Map<String, String>)obj;
// Outputs...
// key0=value3
// key1=value1
for (Map.Entry<String,String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
} catch (ClassCastException e) {
e.printStackTrace();
}
GSON uses MapTypeAdapterFactory to deserialioze map. Below is a short excerpt of its source code where a new entry is put in a map:
V replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
Knowing that there is at least one way to bypass this strict behavior: create your own map that overrides the method put(..) to return always null, like:
public class DuploMap extends HashMap<String, String>{
#Override
public String put(String key, String value) {
super.put(key, value);
return null;
}
}
then deserailizing to it like:
gson.fromJson(JSON, DuploMap.class);
will not throw that exception.
You can use GSON's JsonReader if you do not mind the manual effort.
On the plus side:
faster (no reflection, no casts)
fully under your control
--
String json = "{"key0":"value0","key1":"value1","key0":"value3"}";
JsonReader jsonReader = new JsonReader(new StringReader(json));
HashMap<String,String> map = new HashMap<String, String>()
String currKey;
try {
while(jsonReader.hasNext()){
JsonToken nextToken = jsonReader.peek();
if(JsonToken.NAME.equals(nextToken)){
currKey = jsonReader.nextName();
}
if(JsonToken.STRING.equals(nextToken)){
map.put(currKey, jsonReader.nextString())
}
}
} catch (IOException e) {
e.printStackTrace();
}
I have recently been setting up mobile apps to work with my meteor server. As a part of this I have to pass the meteor web app data from android. Unfortunately I have been receiving a error that tells me that the java object I am passing "would be serialized to null". How do I prevent this?
JSONObject json = new JSONObject();
try{
json.put("Foo", "1");
json.put("Blah", 0);
}catch (JSONException e){
}
Object[] object = new Object[1];
object[0] = json;
System.out.println(object + ", " + object[0] + ", " + object[0].toString());
mMeteor.call("xxx", object, new ResultListener() {
#Override
public void onSuccess(String result) {
}
#Override
public void onError(String error, String reason, String details) {
}
});
}
#Override
public void onError(String error, String reason, String details) {
}
});
Android/Meteor interface Library function
public void callWithSeed(final String methodName, final String randomSeed, final Object[] params, final ResultListener listener) {
// create a new unique ID for this request
final String callId = uniqueID();
// save a reference to the listener to be executed later
if (listener != null) {
mListeners.put(callId, listener);
}
// send the request
final Map<String, Object> data = new HashMap<String, Object>();
data.put(Protocol.Field.MESSAGE, Protocol.Message.METHOD);
data.put(Protocol.Field.METHOD, methodName);
data.put(Protocol.Field.ID, callId);
if (params != null) {
data.put(Protocol.Field.PARAMS, params);
}
if (randomSeed != null) {
data.put(Protocol.Field.RANDOM_SEED, randomSeed);
}
send(data);
}
I was having this same issue, my first error was passing a CharSequence instead a String as a parameter (your Object[]), and my other error was passing an Object[] as another parameter (I solved this by sending a String instead, like : String.valueOf(your_object_list)) Dont forget to handle this on your server side, you will receive a String instead of an Object.
Convert the JSONArray to List & JSONObject to HashMap and then pass those instead of the raw JSONObject or JSONArray.
You can write a recursive function for the conversion in case of nested JSONObject and JSONArray or can use GSON library for the conversion.
For more details about the conversion, this SO post may be helpful.
If I have 'n' number of methods like this, is there a way out by which I can optimize this and make it a single function?
Or are there other better options by which I can make this more generic?
public List<Address> getAddressList(String response) {
List<Address> AddressList = new ArrayList<Address>();
if (response != null && response.length() > 0) {
try {
Gson gson = new Gson();
Type collectionType = new TypeToken<List<Address>>(){}.getType();
AddressList = gson.fromJson(response, collectionType);
} catch (IllegalStateException ex) {
} catch (Exception ex) {
}
}
return AddressList;
}
public List<Tweet> getTweetList(String response) {
List<Tweet> tweetList = new ArrayList<Tweet>();
if (response != null && response.length() > 0) {
try {
Gson gson = new Gson();
Type collectionType = new TypeToken<List<Tweet>>(){}.getType();
tweetList = gson.fromJson(response, collectionType);
} catch (IllegalStateException ex) {
} catch (Exception ex) {
}
}
return tweetList;
}
To duplicate axtavt's answer from this here question:
There is no way to do it without passing actual type of T (as Class<T>) to your method.
But if you pass it explicitly, you can create a TypeToken for List<T> as follows:
private <T> List<T> GetListFromFile(String filename, Class<T> elementType) {
...
TypeToken<List<T>> token = new TypeToken<List<T>>() {}
.where(new TypeParameter<T>() {}, elementType);
List<T> something = gson.fromJson(data, token);
...
}
See also:
TypeToken
So, to answer your question, you could do something like this:
public List<Address> getAddressList(final String response) {
return getGenericList(response, Address.class);
}
public List<Tweet> getTweetList(final String response) {
return getGenericList(response, Tweet.class);
}
#SuppressWarnings("serial")
private <T> List<T> getGenericList(final String response, final Class<T> elementType) {
List<T> list = new ArrayList<T>();
if (response != null && response.length() > 0) {
try {
final Gson gson = new Gson();
final Type collectionType =
new TypeToken<List<T>>(){}.where(new TypeParameter<T>() {}, elementType).getType();
list = gson.fromJson(response, collectionType);
}
catch (final IllegalStateException ex) {
}
catch (final Exception ex) {
}
}
return list;
}
EDIT: Tried the code out
I tried this code out with the following small test that should just create a list of a couple of addresses:
public static void main(final String[] args) {
final List<Address> addressList = getAddressList("[{}, {}]");
System.out.println(addressList);
}
And the output was:
[gson.Address#6037fb1e, gson.Address#7b479feb]
I made my own Address class in my test project, hence the gson.Address in the above output.
I'd like to convert the an Intent's extras Bundle into a JSONObject so that I can pass it to/from JavaScript.
Is there a quick or best way to do this conversion? It would be alright if not all possible Bundles will work.
You can use Bundle#keySet() to get a list of keys that a Bundle contains. You can then iterate through those keys and add each key-value pair into a JSONObject:
JSONObject json = new JSONObject();
Set<String> keys = bundle.keySet();
for (String key : keys) {
try {
// json.put(key, bundle.get(key)); see edit below
json.put(key, JSONObject.wrap(bundle.get(key)));
} catch(JSONException e) {
//Handle exception here
}
}
Note that JSONObject#put will require you to catch a JSONException.
Edit:
It was pointed out that the previous code didn't handle Collection and Map types very well. If you're using API 19 or higher, there's a JSONObject#wrap method that will help if that's important to you. From the docs:
Wrap an object, if necessary. If the object is null, return the NULL
object. If it is an array or collection, wrap it in a JSONArray. If it
is a map, wrap it in a JSONObject. If it is a standard property
(Double, String, et al) then it is already wrapped. Otherwise, if it
comes from one of the java packages, turn it into a string. And if it
doesn't, try to wrap it in a JSONObject. If the wrapping fails, then
null is returned.
private String getJson(final Bundle bundle) {
if (bundle == null) return null;
JSONObject jsonObject = new JSONObject();
for (String key : bundle.keySet()) {
Object obj = bundle.get(key);
try {
jsonObject.put(key, wrap(bundle.get(key)));
} catch (JSONException e) {
e.printStackTrace();
}
}
return jsonObject.toString();
}
public static Object wrap(Object o) {
if (o == null) {
return JSONObject.NULL;
}
if (o instanceof JSONArray || o instanceof JSONObject) {
return o;
}
if (o.equals(JSONObject.NULL)) {
return o;
}
try {
if (o instanceof Collection) {
return new JSONArray((Collection) o);
} else if (o.getClass().isArray()) {
return toJSONArray(o);
}
if (o instanceof Map) {
return new JSONObject((Map) o);
}
if (o instanceof Boolean ||
o instanceof Byte ||
o instanceof Character ||
o instanceof Double ||
o instanceof Float ||
o instanceof Integer ||
o instanceof Long ||
o instanceof Short ||
o instanceof String) {
return o;
}
if (o.getClass().getPackage().getName().startsWith("java.")) {
return o.toString();
}
} catch (Exception ignored) {
}
return null;
}
public static JSONArray toJSONArray(Object array) throws JSONException {
JSONArray result = new JSONArray();
if (!array.getClass().isArray()) {
throw new JSONException("Not a primitive array: " + array.getClass());
}
final int length = Array.getLength(array);
for (int i = 0; i < length; ++i) {
result.put(wrap(Array.get(array, i)));
}
return result;
}
Here is a Gson type adapter factory that converts a Bundle to JSON.
https://github.com/google-gson/typeadapters/blob/master/android/src/main/java/BundleTypeAdapterFactory.java
If the bundle has nested bundles then JSONObject.wrap(bundle.get(key)) will return null. So I managed to get it to work for my use case with this recursive function. Haven't tested more advanced use cases though.
JSONObject json = convertBundleToJson(bundle);
public JSONObject convertBundleToJson(Bundle bundle) {
JSONObject json = new JSONObject();
Set<String> keys = bundle.keySet();
for (String key : keys) {
try {
if (bundle.get(key) != null && bundle.get(key).getClass().getName().equals("android.os.Bundle")) {
Bundle nestedBundle = (Bundle) bundle.get(key);
json.put(key, convertToJson(nestedBundle));
} else {
json.put(key, JSONObject.wrap(bundle.get(key)));
}
} catch(JSONException e) {
System.out.println(e.toString());
}
}
return json;
}
Object myJsonObj = bundleObject.get("yourKey");
JsonParser parser = new JsonParser();
JsonObject json = parser.parse(myJsonObj.toString()).getAsJsonObject();
json.get("memberInJson").getAsString();
private static void createFlatJSon(Bundle appRestrictions, JSONObject jsonObject) throws JSONException{
for (String key : appRestrictions.keySet()) {
if (appRestrictions.get(key) instanceof Bundle) {
Bundle bundle = (Bundle)appRestrictions.get(key);
Map<String, String> map = ((Bundle)appRestrictions.get(key)).keySet().stream().collect(Collectors.toMap(x -> x, x -> bundle.get(x).toString()));
JSONObject jsonNested = new JSONObject(map);
jsonObject.put(key,jsonNested);
//createFlatJSon((Bundle) appRestrictions.get(key),jsonObject);
}else if (appRestrictions.get(key) instanceof Parcelable[]){
for (int i=0;i< ((Parcelable[]) appRestrictions.get(key)).length; i++){
createFlatJSon((Bundle)((Parcelable[]) appRestrictions.get(key))[i],jsonObject);
}
//Log.e("KEY skipped",appRestrictions.get(key).toString());
}else{
// map = appRestrictions.keySet().stream().collect(Collectors.toMap(x -> x, x -> appRestrictions.get(x).toString()));// Use this if don't want to modify the keys
Log.e("KEY: ", key + " Value:" + appRestrictions.getString(key));
Log.e("KEY: ", key + " Value:" + appRestrictions.get(key).getClass().getSimpleName());
if (appRestrictions.get(key) instanceof String[]){
JSONArray jsonArray = new JSONArray();
for (String value : (String[])appRestrictions.get(key)) {
jsonArray.put(value);
}
jsonObject.put(key,jsonArray);
}else {
jsonObject.put(key, appRestrictions.get(key).toString());
}
}
}
}