Imagine having a JSON structure containing a list of purchased products by customers:
{
"product_key_1": {
"name": "product_name_1",
"price": 10.99
},
"product_key_2": {
"name": "product_name_2",
"price": 24.99
},
"product_key_3": {
"name": "product_name_3",
"price": 7.49
}
}
However, in this scenario, the client is solely interested in extracting the keys representing the products and isn't concerned with the specific details like product names or prices.
What would be the recommended approach for deserializing this JSON string using Gson, with the primary goal of obtaining the keys?
Currently, I am employing the following method:
Map<String, Object> products = gson.fromJson(rawJsonString, Object.class);
Set<String> productKeys = products.keySet();
This works but I am wondering if this is the correct way."
EDIT: seems like another way to do this is to convert json to a Map<String, JsonElement>
Map<String, JsonElement> products = gson.fromJson(rawJsonString, new TypeToken<Map<String, JsonElement>>() {}.getType());
If you are only interested in the map keys, deserializing the map values as Object
or JsonElement
is rather wasteful because Gson will still construct the corresponding objects, even if you don't use them.
What you could do instead is create an empty class and specify that as value type (assuming that the map values are always of type JSON object). Because Gson by default ignores unknown fields, it will efficiently skip all the fields in the values of the JSON object members.
The code could then look like this:
class IgnoredValue {
}
Map<String, IgnoredValue> map = gson.fromJson(rawJsonString, new TypeToken<Map<String, IgnoredValue>>() {});
System.out.println(map.keySet());
Additionally you could also register a custom TypeAdapter
for the value type, which always skips the JSON value and returns null
. That might be even more efficient and would also work if the map values are something other than JSON objects (for example JSON arrays, numbers or strings):
Gson gson = new GsonBuilder()
.registerTypeAdapter(IgnoredValue.class, new TypeAdapter<IgnoredValue>() {
@Override
public IgnoredValue read(JsonReader in) throws IOException {
in.skipValue();
return null;
}
@Override
public void write(JsonWriter out, IgnoredValue value) throws IOException {
throw new UnsupportedOperationException();
}
})
.create();
Map<String, IgnoredValue> map = gson.fromJson(rawJsonString, new TypeToken<Map<String, IgnoredValue>>() {});
Alternatively, if this JSON data is at the top level (and not nested deeply inside a larger JSON document), then you could also directly use JsonReader
if you want:
List<String> keys = new ArrayList<>();
try (JsonReader jsonReader = new JsonReader(new StringReader(rawJsonString))) {
jsonReader.beginObject();
// Handle all JSON object members
while (jsonReader.hasNext()) {
keys.add(jsonReader.nextName());
jsonReader.skipValue();
}
jsonReader.endObject();
}
If the JSON data is deeply nested and you have an enclosing deserialized class, then you could add a List<String> products
field, create a TypeAdapter
which contains the above JsonReader
code in its read
method, and add a @JsonAdapter
annotation which refers to your type adapter on that field.