GSON: Custom deserialiser throws java.lang.UnsupportedOperationException: JsonNull [duplicate] - java

Running a Play! app with Scala. I'm doing a request where the response is expected to be a JSON string. When checking the debugger, the JsonElement returns OK with all information as expected. However, the problem is when I try to actually run methods on that JsonElement.
val json = WS.url("http://maps.googleapis.com/maps/api/geocode/json?callback=?&sensor=true&address=%s", startAddress+","+startCity+","+startProvince).get.getJson
val geocoder = json.getAsString
The only error I get back is Unsupported Operation Exception: null and I've tried this on getAsString and getAsJsonObject and getAsJsonPrimitive
Any idea why it's failing on all methods? Thanks.

I had a similar problem and I had to change jsonObject.getAsString() to jsonObject.toString();

Maybe your JsonElement is a JsonNull
What you could do is to first check that it isn't by using json.isJsonNull
Otherwise, try to get its String representation with json.toString

In my case I just needed to get the element as an empty string if it is null, so I wrote a function like this:
private String getNullAsEmptyString(JsonElement jsonElement) {
return jsonElement.isJsonNull() ? "" : jsonElement.getAsString();
}
So instead of
val geocoder = json.getAsString
You can just use this
val geocoder = getNullAsEmptyString(json);
It returns "" if the element is null and the actual string if it is not

To add to #Henry's answer. In the spirit of Kotlins "OrNull" Adding an extension function:
fun JsonElement.asStringOrNull(): String? {
return if (isJsonNull) null else asString
}

The class JsonElement will throw Unsupported Operation Exception for any getAs<Type> method, because it's an abstract class and makes sense that it is implemented in this way.
For some reason the class JsonObject, does not implement the getAs<Type> methods, so any call to one of these methods will throw an exception.
Calling the toString method on a JsonElement object, may solve your issue in certain circumstances, but isn't probably what you want because it returns the json representation as String (e.g. \"value\") in some cases.
I found out that also a JsonPrimitive class exists and it does implement the getAs<Type> methods. So probably the correct way to proceed is something like this:
String input = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
JsonParser parser = new JsonParser();
JsonElement jsonTree = parser.parse(input);
if(jsonTree != null && jsonTree.isJsonObject()) {
JsonObject jsonObject = jsonTree.getAsJsonObject();
value = jsonObject.get("key1").getAsJsonPrimitive().getAsString()
}
PS. I removed all the nullability mgmt part. If you are coding in Java you probably want to manage this in a better way.
see GitHub source code for JsonElement:
https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/JsonElement.java#L178

Related

In Java, how do I mask values of all the matching keys of a JSON, when the structure of JSON isn't same?

I'm looking for a way to hide/mask the sensitive details of JSON output coming from a response, like account number.
All the answers I got over the web requires me to know the JSON structure before hand. Isn't there any way to extensively traverse each key and then replace it's value with required masking character, without knowing the JSON structure beforehand, which means the required key can be within a JSONArray or JSONObject and sometimes within one another.
Check if the following link to handle dynamic Json helps.
Basically the link highlights two options.
a) Using JsonNode
b) Mapping Dynamic properties using a Map
If you are using the com.google.gson.GsonBuilder.GsonBuilder(). You can add Type adapters, using the method registerTypeAdapter(TypeToAdapt.class, myTypeAdapter), where myTypeAdapter extends TypeAdapter<TypeToAdapt>.
Possibly more to your case, you can set exclusion classes, see:
https://howtodoinjava.com/gson/gson-gsonbuilder-configuration/
It is also possible to add bespoke Serialisers and deserialisers by using the #JsonAdapter(BespokeSerialiser.class) or #JsonAdapter(BespokeDeserialiser.class) to your model class.
The BespokeSerialiser or BespokeDeserialiser must implement JsonSerializer<ModelClass> or JsonDeserializer<ModelClass>.
Thank you all for the replies, it gave me an insight into the solution I was looking for. However, me and my colleague wrote the exact method we were looking for. The method we wrote accepts a JSON as an JSON Object, the key to be searched and the mask string by which I want the key's value to be replaced with.
Feel free to contribute on improving the below code.
public static JSONObject maskJSONValue(JSONObject jsonObject, String key, String mask) throws Exception{
Iterator iterator = jsonObject.keys();
String localKey = null;
while (iterator.hasNext()){
localKey = (String) iterator.next();
if((jsonObject.optJSONArray(localKey) == null) && (jsonObject.optJSONObject(localKey) == null)){
if((localKey.equals(key))){
jsonObject.put(localKey, mask);
return jsonObject;
}
}
}
if(jsonObject.optJSONObject(localKey) != null)
maskJSONValue(jsonObject.getJSONObject(localKey), key, mask);
if(jsonObject.optJSONArray(localKey) != null){
JSONArray jArray = jsonObject.getJSONArray(localKey);
for( int i = 0; i < jArray.length(); i++)
maskJSONValue(jArray.getJSONObject(i), key, mask);
}
return jsonObject;
}

How to handle optional keys in JSON response with Java

"app":{
"icon":{
"icon":"TOP_RATED"
},
"message":{
"_type":"TextSpan",
"text":"Top Rated"
}
}
I keep seeing the following code in one of the projects that I have inherited. The JSON response above is parsed as follows
// itemObject has the entire json response
// appObject is a POJO with icon, type fields
String icon= JsonPath.with(itemObject).getAsString("icon/icon");
appObject.setIcon(icon);
String type = "";
try {
type = JsonPath.with(itemObject).getAsString("message/_type");
catch(IllegalArgumentException e) {
// do nothing if type is not found in response
} finally {
// set type to empty string if it's not found
appObject.setType(type);
}
In the scenario, when _type doesn't exist for a specific app, would it be best to surround it with a try/catch block as shown above? It just seems wrong to use try/catch/finally block to process business logic instead of error handling. What is a better way to do the same and can Java 8 Optional help with this?
I find the org.json package simple and straightforward. It is found here. The org.json.JSONObject class, for example, contains the public boolean has(String key) method, which is used to check if a certain key exists.
Returns true if this object has a mapping for name. The mapping may be NULL.
You can check this way where 'HAS' - Returns true if this object has a mapping for name. The mapping may be NULL.
if (json.has("status")) {
String status = json.getString("status"));
}
if (json.has("club")) {
String club = json.getString("club"));
}
You can also check using 'isNull' - Returns true if this object has no
mapping for name or if it has a mapping whose value is NULL.
if (!json.isNull("club"))
String club = json.getString("club"));
http://developer.android.com/reference/org/json/JSONObject.html#has(java.lang.String)

Why JsonNull in GSON?

The docs say the JsonObject#get method returns null if no such member exists. That's not accurate; sometimes a JsonNull object is returned instead of null.
What is the idiom for checking whether a particular field exists in GSON? I wish to avoid this clunky style:
jsonElement = jsonObject.get("optional_field");
if (jsonElement != null && !jsonElement.isJsonNull()) {
s = jsonElement .getAsString();
}
Why did GSON use JsonNull instead of null?
There is an answer for what are the differences between null and JsonNull. In my question above, I'm looking for the reasons why.
Gson, presumably, wanted to model the difference between the absence of a value and the presence of the JSON value null in the JSON. For example, there's a difference between these two JSON snippets
{}
{"key":null}
your application might consider them the same, but the JSON format doesn't.
Calling
JsonObject jsonObject = new JsonObject(); // {}
jsonObject.get("key");
returns the Java value null because no member exists with that name.
Calling
JsonObject jsonObject = new JsonObject();
jsonObject.add("key", JsonNull.INSTANCE /* or even null */); // {"key":null}
jsonObject.get("key");
returns an instance of type JsonNull (the singleton referenced by JsonNull.INSTANCE) because a member does exist with that name and its value is JSON null, represented by the JsonNull value.
I know question is not asking for a solution, but I came here looking for one. So I will post it in case someone else needs it.
Below Kotlin extension code saves the trouble of checking for null and isJsonNull separately for each element
import com.google.gson.JsonElement
import com.google.gson.JsonObject
fun JsonObject.getNullable(key: String): JsonElement? {
val value: JsonElement = this.get(key) ?: return null
if (value.isJsonNull) {
return null
}
return value
}
and instead of calling like this
jsonObject.get("name")
you call like this
jsonObject.getNullable("name")
Works particularly great in nested structures. Your code eventually would look like this
val name = jsonObject.getNullable("owner")?.asJsonObject?.
getNullable("personDetails")?.asJsonObject?.
getNullable("name")
?: ""

Append new value to javax.json.JsonObject

The code that we already have return us JsonObject. What I want to do is to add a new key and the value for it.
For example, we have an object like this:
{"id":"12","name":"test"}
I want to transform it into this:
{"id":"12","name":"test","status":"complete"}
I didn't find what I need in documentation except using put method. So I wrote this code:
JsonObject object = getJsonObject();
JsonString val = new JsonString() {
public JsonValue.ValueType getValueType() {
return JsonValue.ValueType.STRING;
}
public String getString() {
return "complete";
}
public CharSequence getChars() {
return (CharSequence) "complete";
}
};
object.put("status", val);
But it doesn't work, crashing with :
java.lang.UnsupportedOperationException
I can't understand what is wrong. Have I any other option to complete such a task?
I don't think JsonObject instances are meant to be modified.
I think your best option is to create a new object, copy the existing properties and add the new property to it.
You can use https://docs.oracle.com/javaee/7/api/javax/json/JsonObjectBuilder.html
Not sure if object.put works but you can use the following way to append the details to JSON value:
You can create a different JSON object with the key and value that you want to add to the JSON object and the user object.merge(status, complete, String::concat);
merge() checks for the key:'status' in your JSON object if it does'nt find it then it adds that key:value pair or else it replaces it
.You are not able to compile it because you may not be using jre 1.8.
I've Just verified the following method:
Just create a new JSONObject(org.json.JSONObject not javax.json.JsonObject)
JSONObject modifiedJsonObject= new JSONObject(object.toString());
modifiedJsonObject.put("status", "complete");

How to ignore case when searching a JSON Object

My sample JSON input is as follows:
"JobName":"Test Job 1",
"events":[
{ "features":[],
"InputHiveTable":"uilog_uiclientlogdata",
"eventColumn":"command",
"name":"edu.apollogrp.classroom.discussion.client.events.CreateDiscussionEvent"
},
Consider the field "InputHiveTable", it could be in all uppercase INPUTHIVETABLE, all lowercase inputhivetable, or a mixture of both as it is now.
Currently, I'm reading the field as follows (in Java):
JSONObject jsonObject = (JSONObject) obj;
JSONArray events = (JSONArray) jsonObject.get("events");
String InputHiveTable = (String)event.get("InputHiveTable");
So my question is how do I search for the field "InputHiveTable" while ignoring the case.
I'm using JSON Simple libraries.
If you have to perform this case-insensitive lookup many times, I'd just write a method to do that lookup:
public Object getIgnoreCase(JSONObject jobj, String key) {
Iterator<String> iter = jobj.keySet().iterator();
while (iter.hasNext()) {
String key1 = iter.next();
if (key1.equalsIgnoreCase(key)) {
return jobj.get(key1);
}
}
return null;
}
You could read the JSONObject into a java string, and call String.toLowerCase on it and store it back into a JSONObject. This will turn the entire case of the string to lower case, so you will have to account for that elsewhere in your logic. After that, you then would just have to do a get call on "inputhivetable".
By no means is it a pretty solution, but it is a potential work around if there is absolutely no other way for you to handle what you're returning as your JSON input.
Given that case-insensitivity can be achieved with TreeMap (i.e. via String.CASE_INSENSITIVE_ORDER comparator), you can probably do the following:
Implement your own MyJSONObject extending TreeMap where its methods will be just calling static methods of JSONObject with the same signatures and all required interfaces as in JSONObject. In default constructor write super(String.CASE_INSENSITIVE_ORDER)
Implement ContainerFactory interface where createObjectContainer will return new instance of MyJSONObject (and createArrayContainer will just return new JSONArray).
To run it with new container MyContainerFactory:
StringReader in = new StringReader(yourJSONString);
JSONParser parser = new JSONParser();
parser.parse(in, yourContainerFactory)

Categories

Resources