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.
Related
Very new so bear with me.
The method BloodPressure() is called in MainActivity and the values are fetched and "put" into the jsonObject.
Method getBloodPressure() will return this jsonobject and is called in another module.
The jsonobject in getBloodPressure() is always empty {}.
I have try to debug. So I can see the json is created exactly. But when I "return jsonObject" in getBloodPressure it just resets and becomes {}.
How do I call the jsonObject with bloodPressure values?
MainActivity
private static void dumpDataSet(DataSet dataSet) {
.
.
.
healthdata.BloodPressure(type, systolic, diastolic, sdate, edate );
//printed this and it works perfectly fine
}
HealthData.java
public class HealthData {
private String steps,heartRate,systolic, diastolic, bloodGlucose,bodyTemperature,age, startDate, endDate, dataType;
JSONObject jsonObject = new JSONObject();
public HealthData(){
super();
}
public void BloodPressure(String type, String sys, String dia, String sdate, String edate) {
dataType = type;
systolic = sys;
diastolic = dia;
startDate = sdate;
endDate = edate;
try {
jsonObject.put("dataType", dataType);
jsonObject.put("SystolicValue", systolic);
jsonObject.put("DiastolicValue", diastolic);
jsonObject.put("startDate", startDate);
jsonObject.put("endDate", endDate);
Log.v("json:", String.valueOf(this.jsonObject)); //prints json successfully
} catch (Exception e) {
Log.v(e.toString(), null);
}
}
public JSONObject getBloodPressure() throws JSONException {
return jsonObject; //trying to return json but its empty
}
}
AnotherModule:
public void getHealthData(Callback cb) {
try {
HealthData healthData = new HealthData();
JSONObject json = healthData.getBloodPressure(); ///calling it here
cb.invoke(null, json);
} catch (Exception e){
cb.invoke(e.toString(), null);
}
}
I saw your code and found you didn't call BloodPressure() method before getBloodPressure().
I think that you should call BloodPressure() method before getBloodPressure()
So your another module code like this.
public void getHealthData(Callback cb) {
try{
HealthData healthData = new HealthData();
healthData.BloodPressure("type", "sys", "dia", "sdate", "edate") // you must call this method.
JSONObject json = healthData.getBloodPressure(); ///calling it here
cb.invoke(null, json);
} catch (Exception e) {
cb.invoke(e.toString(), null);
}
}
Update:
I think you should not create new instance of HealthData in getHealthData() method.
public void getHealthData(Callback cb) {
try{
//HealthData healthData = new HealthData(); // this line is wrong. So I removed this line.
JSONObject json = healthData.getBloodPressure(); ///calling it here
cb.invoke(null, json);
} catch (Exception e) {
cb.invoke(e.toString(), null);
}
}
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 am facing issue in converting the nested list object to JSON.I am using object mapper and it is only converting the starting values and after that there is one arraylist inside it and it is not going through that list.
I have tried some basic iteration using JsonNode root = mapper.valueToTree(obj)so that i can iterate through the inner arraylist but i am not getting the result.I am new to this parsing conversion.
code snippet--
public class JsonUtils {
public static <T> String toJsonString(final T obj) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
String jsonString = null;
try {
//JsonNode root = mapper.valueToTree(obj);
jsonString = mapper.writeValueAsString(obj);
} catch (final JsonProcessingException e) {
throw e;
} catch (IOException e) {
throw e;
}
return jsonString;
}
public static <T> String toJsonString(final List<T> lstObject) throws JSONException, IOException {
final JSONArray jsonArray = new JSONArray();
for (final T object : lstObject) {
final String json = JsonUtils.toJsonString(object);
final JSONObject jsonObj = new JSONObject(json);
jsonArray.put(jsonObj);
}
return jsonArray.toString();
}
}
So here is the result which i am getting -
[2, [{"geoMarketId":1,"geoname":"AP","geoId":1,"checked":false},
{"geoMarketId":7,"geoname":"EP","geoId":2,"checked":false},
{"geoMarketId":16,"geoname":"Japan","geoId":3,"checked":true},
{"geoMarketId":18,"geoname":"LA","geoId":4,"checked":true},
{"geoMarketId":22,"geoname":"MEA","geoId":5,"checked":true},
{"geoMarketId":24,"geoname":"NA","geoId":6,"checked":false}]]
Actual Result which should come-
{"geoMarketId":1,"geoname":"AP","geoId":1,"checked":false,
marketName:{"marketname":JP,"marketname":"AP","marketname":MP}}
My json conversion is ignoring this inner list in the same index.
Is there any way my json class can iterate and also convert that innerlist to JSON?
I created a moshi adapter to address null String values:
public class NullStringAdapter {
#FromJson
public String stringFromJson(#Nullable String value) {
if (value.equals(null)) {
return "nulled";
}
return value;
}
}
I created a Moshi instance with it and added it to my retrofit:
Moshi moshi = new Moshi.Builder().add(new NullStringAdapter()).build();
Retrofit.Builder().baseUrl(baseURL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
At runtime, I get a StackOverflowError from a repetitive method in the MoshiAdapterMethodsFactory:
Caused by: java.lang.StackOverflowError: stack size 1038KB
at com.squareup.moshi.Moshi.cacheKey(Moshi.java:140)
at com.squareup.moshi.Moshi.adapter(Moshi.java:69)
at com.squareup.moshi.AdapterMethodsFactory$5.fromJson(AdapterMethodsFactory.java:212)
at com.squareup.moshi.AdapterMethodsFactory$1.fromJson(AdapterMethodsFactory.java:81)
at com.squareup.moshi.AdapterMethodsFactory$5.fromJson(AdapterMethodsFactory.java:212)
at com.squareup.moshi.AdapterMethodsFactory$1.fromJson(AdapterMethodsFactory.java:81)
at com.squareup.moshi.AdapterMethodsFactory$5.fromJson(AdapterMethodsFactory.java:212)
at com.squareup.moshi.AdapterMethodsFactory$1.fromJson(AdapterMethodsFactory.java:81)
.... and so on.
The two problem code areas are line 212:
#Override public Object fromJson(Moshi moshi, JsonReader reader)
throws IOException, IllegalAccessException, InvocationTargetException {
JsonAdapter<Object> delegate = moshi.adapter(parameterTypes[0], qualifierAnnotations);
*****Object intermediate = delegate.fromJson(reader);*****
return method.invoke(adapter, intermediate);
}
And line 81:
#Override public Object fromJson(JsonReader reader) throws IOException {
if (fromAdapter == null) {
return delegate.fromJson(reader);
} else if (!fromAdapter.nullable && reader.peek() == JsonReader.Token.NULL) {
reader.nextNull();
return null;
} else {
try {
*****return fromAdapter.fromJson(moshi, reader);*****
} catch (IllegalAccessException e) {
throw new AssertionError();
} catch (InvocationTargetException e) {
if (e.getCause() instanceof IOException) throw (IOException) e.getCause();
throw new JsonDataException(e.getCause() + " at " + reader.getPath());
}
}
}
Why is the method factory endlessly generating these methods?
The problem is that you are trying to adapt String to String, that's what creates the loop thus resulting in a StackOverflowError. Create a custom annotation for all the strings that you expect to have the "nulled" value, smth like this:
#JsonQualifier
#Retention(RetentionPolicy.RUNTIME)
public #interface NullableString { }
And then change your adapter to:
public class NullStringAdapter {
#FromJson #NullableString
public String stringFromJson(#Nullable String value) {
if (value.equals(null)) {
return "nulled";
}
return value;
}
}
Or if you want to enforce this type or deserializing to every String in you parse from json (altho I would not advice for that) you can declare your adapter using a factory:
public static final JsonAdapter.Factory FACTORY = new JsonAdapter.Factory() {
#Override public JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
if (!annotations.isEmpty()) return null;
Class<?> rawType = Types.getRawType(type);
if (rawType.equals(String.class) return /** your custom JsonAdapter here. */;
return null;
}
};
I would like to mask certain elements of JSON and print to logs. Masking can be either by substituting by dummy data or removing the key pair .Is there a utility to do the masking in Java ?
E.g.,
given JSON:
{
"key1":"value1",
"key2":"value2",
"key3":"value3",
}
mask key 2 alone and print JSON:
{
"key1":"value1",
"key2":"xxxxxx",
"key3":"value3",
}
or
{
"key1":"value1",
"key3":"value3",
}
input will be JSON object or array type in string format. Here the maskable keys only static otherwise input string will dynamic.
public final class MaskPIData {
/**
* Mask able keywords mentioned here. It should be in LOWER CASE.
*/
private static final Set<String> MASKABLE_KEYS = new HashSet<>(Arrays.asList(
"email",
"emails",
"phone",
"pin",
"password",
"phonenumber",
"moneys"));
private static final String MASKING_VALUE = "****";
private static final ObjectMapper OBJECTMAPPER = new ObjectMapper();
private MaskPIData() {
super();
}
private static boolean isValidSet(Set<String> set) {
return set != null && !set.isEmpty();
}
private static boolean isKnownPrimitiveWrapperModel(Object obj) {
return obj == null || obj instanceof String || obj instanceof Integer || obj instanceof Long
|| obj instanceof Double;
}
#SuppressWarnings("unchecked")
private static JSONObject maskingForJsonObject(Set<String> maskableKeys, JSONObject input) {
if (!isValidSet(maskableKeys) || input == null) {
return input;
}
Map<String, Object> inputMap = (Map<String, Object>) input;
Map<String, Object> caseInsensitiveInputMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
caseInsensitiveInputMap.putAll(inputMap);
for (Map.Entry<String, Object> entryPair : caseInsensitiveInputMap.entrySet()) {
if (entryPair.getValue() instanceof JSONArray) {
JSONArray jsonArr = (JSONArray) caseInsensitiveInputMap.get(entryPair.getKey());
maskingForArray(maskableKeys, entryPair.getKey(), jsonArr);
caseInsensitiveInputMap.put(entryPair.getKey(), jsonArr);
} else if (entryPair.getValue() instanceof JSONObject) {
JSONObject jsonObj = (JSONObject) caseInsensitiveInputMap.get(entryPair.getKey());
caseInsensitiveInputMap.put(entryPair.getKey(), maskingForJsonObject(maskableKeys, jsonObj));
} else if (entryPair.getKey() != null && maskableKeys.contains(entryPair.getKey().toLowerCase())) {
caseInsensitiveInputMap.put(entryPair.getKey(), MASKING_VALUE);
}
}
return OBJECTMAPPER.convertValue(caseInsensitiveInputMap, JSONObject.class);
}
#SuppressWarnings("unchecked")
private static JSONArray maskingForArray(Set<String> maskableKeys, String key,
JSONArray jsonArr) {
JSONArray toRet = jsonArr;
for (int idx = 0; idx < toRet.size(); idx++) {
Object obj = toRet.get(idx);
if (isKnownPrimitiveWrapperModel(obj)) {
if (key != null && maskableKeys.contains(key.toLowerCase())) {
toRet.remove(idx);
toRet.add(idx, MASKING_VALUE);
}
} else {
JSONObject jsonObjFromArray = (JSONObject) toRet.get(idx);
JSONObject maskedJsonObj = maskingForJsonObject(maskableKeys, jsonObjFromArray);
toRet.remove(idx);
toRet.add(idx, maskedJsonObj);
}
}
return toRet;
}
public static String doMask(String input) {
String maskedData = input;
if (maskedData != null && !maskedData.trim().isEmpty()) {
try {
if (new JSONParser().parse(maskedData) instanceof JSONObject) {
JSONObject maskedOutput = maskingForJsonObject(MASKABLE_KEYS,
(JSONObject) new JSONParser().parse(maskedData));
maskedData = OBJECTMAPPER.writeValueAsString(maskedOutput);
} else if (new JSONParser().parse(maskedData) instanceof JSONArray) {
JSONArray maskedOutput = maskingForArray(MASKABLE_KEYS, null, (JSONArray) new JSONParser().parse(maskedData));
maskedData = OBJECTMAPPER.writeValueAsString(maskedOutput);
}
} catch (Exception e) {
// to do - Error while masking data
}
}
return maskedData;
}
public static void main(String args[]) {
String input = "{\"item\":{\"test\":\"test\",\"phone\":\"993244\",\"email\":\"mail#mail.com\"}}";
System.out.println(doMask(input));
}
You could use jackson to convert json to map, process map and convert map back to json.
For example:
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
public void mask() throws IOException {
String jsonString = "{\n" +
" \"key1\":\"value1\",\n" +
" \"key2\":\"value2\",\n" +
" \"key3\":\"value3\"\n" +
"}";
Map<String, Object> map;
// Convert json to map
ObjectMapper mapper = new ObjectMapper();
try {
TypeReference ref = new TypeReference<Map<String, Object>>() { };
map = mapper.readValue(jsonString, ref);
} catch (IOException e) {
System.out.print("cannot create Map from json" + e.getMessage());
throw e;
}
// Process map
if(map.containsKey("key2")) {
map.put("key2","xxxxxxxxx");
}
// Convert back map to json
String jsonResult = "";
try {
jsonResult = mapper.writeValueAsString(map);
} catch (IOException e) {
System.out.print("cannot create json from Map" + e.getMessage());
}
System.out.print(jsonResult);