Somehow from one App release into another the internals of org.json library broke. It could no longer read the contents of a key as a string. Here is an example of the code that used to work and is breaking when minify/R8/proguard is enabled.
try {
parseJson("{\"rewards\":[{\"amount\":1,\"name\":\"Add Audio\"}]}");
} catch (Exception e) {
Log.w("TEST", "Error parsing rewarded currencies JSON header!!!", e);
}
private void parseJson(@NonNull String rewardedCurrencies) throws JSONException {
final Map<String, String> rewardsMap = jsonStringToMap(rewardedCurrencies);
for (Map.Entry<String, String> entry : rewardsMap.entrySet()) {
Log.e("TEST", entry.getKey() + ":" + entry.getValue());
}
}
public Map<String, String> jsonStringToMap(String jsonParams) throws JSONException {
Map<String, String> jsonMap = new HashMap<String, String>();
if (TextUtils.isEmpty(jsonParams)) return jsonMap;
JSONObject jsonObject = (JSONObject) new JSONTokener(jsonParams).nextValue();
Iterator<String> keys = jsonObject.keys();
Log.e("TEST", "jsonStringToMap() -> jsonObject: "+jsonObject.toString());
while (keys.hasNext()) {
String key = (String) keys.next();
Log.e("TEST", "jsonStringToMap() -> key: "+key);
jsonMap.put(key, jsonObject.getString(key));
}
return jsonMap;
}
Log output with failure:
jsonStringToMap() -> jsonObject: {"rewards":[{"amount":1,"name":"Add Audio"}]}
jsonStringToMap() -> key: rewards
Error parsing rewarded currencies JSON header!!!
j.b.b: JSONObject["rewards"] not a string.
at j.b.d.getString(SourceFile:4)
So what changed on the project since the last working App release?
targetSdkVersion 28 -> 29
sourceCompatibility JavaVersion.VERSION_1_7 -> JavaVersion.VERSION_1_8
'com.android.tools.build:gradle:4.0.0' -> 'com.android.tools.build:gradle:4.1.1'
distributionUrl -> gradle-6.1.1-all.zip -> gradle-6.5-all.zip
The fix was to apply a new line to the proguard script.
-keep class org.json.** { *; }
And now I'm worried. Why do I need to apply this to proguard? Are there other internal libraries potentially breaking? I can't seem to figure out what has caused this and if it's a potential big issue?
The only reference I could find to the Exception text in your error log is from here: JSONObject not a String Error
I couldn't find a code-path from the Android org.json implementation that would lead to that Exception. This means that
org.json
is somehow getting packaged into your App. This would also mean that it's not a standard library problem as you've feared.Further reinforcing that point: https://github.com/stleary/JSON-java/blob/master/src/main/java/org/json/JSONObject.java#L849-L864
This explains your issue exactly. The implementation of
JSONObject#getString(String key)
differs to the Android implementation. You can only read String values of an Object this way. Any other value will be rejected and cause the Exception. Since you are reading aJSONArray
this will not work.My example below will fix this issue whether you're using the Android Standard Library or the pulled dependency.
The reason why it works without ProGuard is that Androids org.json is already provided by the System. When minifying, it will rename the dependency org.json package and thus force you to use it.
I would recommend getting rid of the
org.json
dependency. Alternatively write code that works with both... You may want to switch toGson
orJackson
anyways since it's a lot easier to use.