Acessing JSON elements by key using GSON - java

What I have: I am completely new to working with JSON files. I am currently using Gson to create two HashMaps<Key, List<Pair>> to store values into. I have gotten to the point where I can create a JSON file.
HashMap<Integer, List<Pair>> in = new HashMap<Integer, List<Pair>>();
HashMap<Integer, List<Pair>> out = new HashMap<Integer, List<Pair>>();
for (int i = 0; i < 300000, i++) {
List<Integer> inList = new ArrayList<Pair>();
List<Integer> outList = new ArrayList<Pair>();
in.put(i, inList);
out.put(i, outList);
}
gsonFile = new GsonBuilder().setPrettyPrinting().create();
FileWriter file = new FileWriter("newFile.json");
gsonFile.toJson(in, file);
gsonFile.toJson(out, file);
file.flush();
class Pair {
int id;
int value;
//Some code here
}
Which gives me to the following JSON file:
{
"0": [],
"1": [],
"2": [],
"3": [],
...
"300000": []
}
{
"0": [],
"1": [],
"2": [],
"3": [],
...
"300000": []
}
What I want: From here, I am wondering how I can use Gson to access for example List<Pair> for Key = 3 in the first HashMap to then add Pair objects into it, and then write back into the file. For example, something like:
List<Pair> list3 = gsonFile.get(0).get("3");
list3.add(new Pair(1, 1));
to become:
{
"0": [],
"1": [],
"2": [],
"3": [{"id": 1, "value": 1}],
...
"300000": []
}
{
"0": [],
"1": [],
"2": [],
"3": [],
...
"300000": []
}
I am using this method to store values as I am expected to have anywhere between 0 to 300,000 Pair objects in a single List<Pair> and therefore, keeping in and out on memory would result in out of memory errors.

Related

How to create a JsonObject of complex structure like given below in Java?

{
"A": {
"B": {
"C": {
"D": "123"
},
"E": {
"F": {
"G": "aaabbb"
},
"H": "QWE",
"I": {
"J": "003",
"K": "01"
}
},
"L": {
"M": {
"N": "1603",
"O": "QWE"
},
"P": "N",
"Q": "N"
},
"R": {
"S": "10000",
"T": "QWE"
},
"U": {
"V": "0",
"W": "13"
},
"X": {
"Y": "123456"
}
},
"ABC": {
"QQ": "5",
"WW": "O"
}
}
}
I want to create a similar Json String without having to create all these classes A, B, C, etc. I could create it by creating classes for each of these tags A, B, C, etc using ObjectMapper of Jackson Library. Is there a simpler way to achieve this?
As already stated in my comment, there are a couple of ways.
Nested Maps
ObjectMapper om = new ObjectMapper();
Map<String, Map<String, Map<String, Map<String, Object>>>> map = new HashMap<>();
map.computeIfAbsent("A", k -> new HashMap<>())
.computeIfAbsent("B", k -> new HashMap<>())
.computeIfAbsent("C", k -> new HashMap<>())
.put("D", "123");
((Map<String, String>)map.computeIfAbsent("A", k -> new HashMap<>())
.computeIfAbsent("B", k -> new HashMap<>())
.computeIfAbsent("E", k -> new HashMap<>())
.computeIfAbsent("F", k -> new HashMap<String, String>()))
.put("G", "aaabbb");
String json = om.writeValueAsString(map);
Methods like computeIfAbsent() help a lot in in having to check for the presence of intermediate maps all the time but as you can see, if the structure is not uniform this can get awkward to handle.
Of course you can build the maps in other ways, this is just a simple demonstration.
Using ObjecNode (and ArrayNode if required)
ObjectMapper om = new ObjectMapper();
ObjectNode root = om.createObjectNode();
ObjectNode nodeA = om.createObjectNode();
root.set("A", nodeA);
ObjectNode nodeB = om.createObjectNode();
nodeA.set("B", nodeB);
ObjectNode nodeC = om.createObjectNode();
nodeB.set("C", nodeC);
nodeC.put("D", "123");
ObjectNode nodeE = om.createObjectNode();
nodeB.set("E", nodeE);
ObjectNode nodeF = om.createObjectNode();
nodeE.set("F", nodeF);
nodeF.put("G", "aaabbb");
String json = om.writeValueAsString(root);
Again there are multiple ways to construct this node hierarchy.
Both examples produce the following (unformatted) json which reflects the first portion of your structure:
{"A":{"B":{"C":{"D":"123"},"E":{"F":{"G":"aaabbb"}}}}}

How to remove in a JSON an Object in an Array if this object contains a key with certain value?

I have a nested Json that I need to remove some objects in array with a filter, this in a dynamic way, this Json structure is not the same all time, for example:
{
"A": "HI",
"B": 1,
"C": [
{
"TIME": "TODAY",
"LOCATION": "USA",
"BALANCE": 100,
"STATE": "TX",
"NAME": "JHON"
},
{
"TIME": "YESTERDAY",
"LOCATION": "USA",
"BALANCE": 100,
"STATE": "TX",
"NAME": "MICHAEL"
},
{
"TIME": "YESTERDAY",
"LOCATION": "USA",
"BALANCE": 100,
"STATE": "TX",
"NAME": "REBECCA"
}
]
}
And now, from this kind of nested Json I want to remove the Object that contains key "NAME" with VALUE "Michael", and the result have to be this one:
{
"A": "HI",
"B": 1,
"C": [
{
"TIME": "TODAY",
"LOCATION": "USA",
"BALANCE": 100,
"STATE": "TX",
"NAME": "JHON"
},
{
"TIME": "YESTERDAY",
"LOCATION": "USA",
"BALANCE": 100,
"STATE": "TX",
"NAME": "REBECCA"
}
]
}
This JSON change every time depending on reponse from an API, just I have to match KEY - VALUE to remove the Object that I need filter without modify the Json structure, in this case I need to recive KEY = "NAME" and VALUE = "Michael" to filter this object.
In this case "C" is a variable key and I could have more keys with arrays in the same json that need to be filtered, I need a dynamic way to filter in array of objects based just in key-value
Could you help me find a way to perform this functionality?
Here is a streaming solution that can deal with huge responses without any significant impact on your system. It also does not require any class mappings using the built-in JSON node representation (therefore saving time and probably memory on type bindings).
public static void filterAbcBySpecializedStreaming(final JsonReader input, final JsonWriter output)
throws IOException {
input.beginObject();
output.beginObject();
// iterating over each entry of the outer object
while ( input.hasNext() ) {
final String name = input.nextName();
output.name(name);
switch ( name ) {
// assuming "A" is known to be a string always
case "A":
output.value(input.nextString());
break;
// assuming "B" is known to be a number always
case "B":
// note: JsonReader does not allow to read a number of an arbitrary length as an instance of `java.lang.Number`
output.value(new LazilyParsedNumber(input.nextString()));
break;
// assuming "C" is known to be an array of objects always
case "C":
input.beginArray();
output.beginArray();
// iterating over each element of the array
while ( input.hasNext() ) {
// assuming array elements are not very big and it trusts their size
final JsonObject jsonObject = Streams.parse(input)
.getAsJsonObject();
// if the current element JSON object has a property named "NAME" and its value is set to "MICHAEL", the skip it
// of course, this can also be externalized using the Strategy design pattern (e.g. using java.lang.function.Predicate)
// but this entire method is not that generic so probably it's fine
if ( jsonObject.get("NAME").getAsString().equals("MICHAEL") ) {
continue;
}
Streams.write(jsonObject, output);
}
input.endArray();
output.endArray();
break;
default:
throw new IllegalStateException("Unknown: " + name);
}
}
input.endObject();
output.endObject();
}
The test:
final JsonElement expected = Streams.parse(expectedJsonReader);
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
final JsonWriter output = new JsonWriter(new OutputStreamWriter(buffer));
Filter.filterAbcBySpecializedStreaming(input, output);
output.flush();
final JsonElement actual = JsonParser.parseReader(new InputStreamReader(new ByteArrayInputStream(buffer.toByteArray())));
Assertions.assertEquals(expected, actual);
Of course, it's not that easy, but it may result in the best performance. Making it generic and "dynamic" is an option, and it can be done according to your needs. If you find it too complex, the input JSON document is known not to be very big (therefore causing OutOfMemoryErrors), you can also filter it out as a tree, but again without any type bindings:
public static void filterAbcBySpecializedTree(final JsonElement input, final JsonElement output) {
final JsonObject inputJsonObject = input.getAsJsonObject();
final JsonObject outputJsonObject = output.getAsJsonObject();
for ( final Map.Entry<String, JsonElement> e : inputJsonObject.entrySet() ) {
final String name = e.getKey();
final JsonElement value = e.getValue();
switch ( name ) {
case "A":
case "B":
outputJsonObject.add(name, value.deepCopy());
break;
case "C":
final JsonArray valueJsonArray = value.getAsJsonArray()
.deepCopy();
for ( final Iterator<JsonElement> it = valueJsonArray.iterator(); it.hasNext(); ) {
final JsonObject elementJsonObject = it.next().getAsJsonObject();
if ( elementJsonObject.get("NAME").getAsString().equals("MICHAEL") ) {
it.remove();
}
}
outputJsonObject.add(name, valueJsonArray);
break;
default:
throw new IllegalStateException("Unknown: " + name);
}
}
}
Test:
final JsonElement input = Streams.parse(inputJsonReader);
final JsonElement expected = Streams.parse(expectedJsonReader);
final JsonElement actual = new JsonObject();
Filter.filterAbcBySpecializedTree(input, actual);
Assertions.assertEquals(expected, actual);

JSON to Java: How to model lists of objects into generic model

I'm making a spreadSheet using SpreadJS, and I should be able to to add, delete and change the value of a key nested inside many objects. Here is how my json is formatted:
{
"version": "10.0.0",
"sheets": {
"Sheet1": {
"name": "Sheet1",
"data": {
"dataTable": {
"0": {
"0": {
"value": 129
}
}
}
},
"selections": {
"0": {
"row": 0,
"rowCount": 1,
"col": 0,
"colCount": 1
},
"length": 1
},
"theme": "Office",
"index": 0
}
}
}
The data represents, say, the value of each cell in the spreadSheet [0,0], [0,1], [1,1] ...etc. I want to parse this data into a List of generic model, for the field dataTable i would like to represent it like this: Map<Integer, Map<Integer, ValueObj>> for example in this case <0, <0, 129>> but i didn 't find how to do that and how my model would likely be.
I am new to JSON any help is appreciated! Thanks
Then to handle data, you can have a generic class like :
class CellData<T> {
T data;
}
Then read as below :
String jsonInput = "{ \"0\": { \"0\": { \"value\": 129 } } }";
ObjectMapper mapper = new ObjectMapper();
TypeReference<HashMap<Integer,HashMap<Integer,CellData<Integer>>>> typeRef =
new TypeReference<HashMap<Integer, HashMap<Integer, CellData<Integer>>>>() {};
Map<Integer, Map<Integer, CellData<Integer>>> map = mapper.readValue(jsonInput, typeRef);

Parse JSON with dynamic key object inside dynamic key object with Gson

I am a newbie to android and I have a JSON file with dynamic key like this:
{
"x": {
"a": {
"1": [1,2,3,4],
"2": [1,2,3,4]
},
"b": {
"1": [1,2,3,4],
"2": [1,2,3,4]
}
},
"y": {
"a": {
"1": [1,2,3,4],
"2": [1,2,3,4]
},
"b": {
"1": [1,2,3,4],
"2": [1,2,3,4]
}
},
"z": {
"a": {
"1": [1,2,3,4],
"2": [1,2,3,4]
},
"b": {
"1": [1,2,3,4],
"2": [1,2,3,4]
}
}
}
I parsed it successfully by JSONObject but I have to loop by keys Iterator on x, y, z. For each time, I have to loop on a, b and the same for "1" and "2". I think it's not a good solution. I created models for them like this:
Class XYZ {
private String name; // "x", "y", "z" value
private ArrayList<ABC> abcList;
}
Class ABC {
private String name; // "a", "b", "c"
private ArrayList<Item> itemList;
}
Class Item{
private String ID; // "1", "2"
private int[] valueArray;
}
Can anyone help me to parse this json by Gson, I think it looks more professional :D. Thank you so much
Your models cannot map your JSON just because Gson default configuration clearly gets them unmatched.
You can have two "default" ways:
static
... since you didn't really mention why your JSON is considered dynamic:
final class XYZ {
final ABC x = null;
final ABC y = null;
final ABC z = null;
}
final class ABC {
final OneTwo a = null;
final OneTwo b = null;
}
final class OneTwo {
#SerializedName("1")
final List<Integer> one = null;
#SerializedName("2")
final List<Integer> two = null;
}
Example:
try ( final Reader reader = getPackageResourceReader(Q43695739.class, "dynamic.json") ) {
final XYZ xyz = gson.fromJson(reader, XYZ.class);
System.out.println(xyz.x.b.two);
}
dynamic (by deserialization)
... assuming your keys are dynamic, but the structure remains the same:
private static final Type stringToStringToStringToIntegerListType = new TypeToken<Map<String, Map<String, Map<String, List<Integer>>>>>() {
}.getType();
try ( final Reader reader = getPackageResourceReader(Q43695739.class, "dynamic.json") ) {
final Map<String, Map<String, Map<String, List<Integer>>>> m = gson.fromJson(reader, stringToStringToStringToIntegerListType);
System.out.println(m.get("x").get("b").get("2"));
}
dynamic (by JSON trees)
Another true dynamic approach that may be helpful for some scenarios. Also note that JSONObject is not in the Gson realm: you probably might have imported this one from the org.json package. Gson uses camel-cased names like JsonElement, JsonObject, etc.
try ( final Reader reader = getPackageResourceReader(Q43695739.class, "dynamic.json") ) {
final JsonElement jsonElement = gson.fromJson(reader, JsonElement.class)
.getAsJsonObject()
.getAsJsonObject("x")
.getAsJsonObject("b")
.getAsJsonArray("2");
System.out.println(jsonElement);
}
The first and the second examples produce java.util.List instances
[1, 2, 3, 4]
The third example returns a JsonArray instance with a slightly different toString implementation:
[1,2,3,4]

Get sub-array from JSON

I parsing some data from a json file. Here is my JSON File.
[
{
"topic": "Example1",
"contact": [
{
"ref": [
1
],
"corresponding": true,
"name": "XYZ"
},
{
"ref": [
1
],
"name": "ZXY"
},
{
"ref": [
1
],
"name": "ABC"
},
{
"ref": [
1,
2
],
"name":"BCA"
}
] ,
"type": "Presentation"
},
{
"topic": "Example2",
"contact": [
{
"ref": [
1
],
"corresponding": true,
"name": "XYZ"
},
{
"ref": [
1
],
"name": "ZXY"
},
{
"ref": [
1
],
"name": "ABC"
},
{
"ref": [
1,
2
],
"name":"BCA"
}
] ,
"type": "Poster"
}
]
I can fetch and store data one by one. Like this one
JSONArray getContactsArray = new JSONArray(jsonObject.getString("contact"));
for(int a =0 ; a < getContactsArray.length(); a++)
{
JSONObject getJSonObj = (JSONObject)getContactsArray.get(a);
String Name = getJSonObj.getString("name");
}
1)Now, my question is there any way to get all name values for each array with single query.
2) Can I get all those values in an Array ?
Please correct me, if I am doing anything wrong. Thank you.
Iteration cannot be avoided here as org.json and other Json parsers as well provide random access to objects but not to their properties collectively (as a collection). So, you can't query something like "all name properties of all contact objects" unless you probably get a Json parser like Gson to unmarshall it that way.
But, that's too much to just avoid a for loop when you can definitely shorten the parse by making use of the appropriate API methods to avoid unnecessary object casts.
JSONArray contacts = jsonObject.getJSONArray("contact");
String[] contactNames = new String[contacts.length()];
for(int i = 0 ; i < contactNames.length; i++) {
contactNames[i] = contacts.getJSONObject(i).getString("name");
}
Better to use a json parser such as GSon or Jackson to marshall your json to a java object. Then you can write utitlity method in your java class to retrieve all the names in that object.
Try this:
Create JSONObject of your file and try to get array of all names and iterate it to get all values.
public static String[] getNames(JSONObject jo) {
int length = jo.length();
if (length == 0) {
return null;
}
Iterator i = jo.keys();
String[] names = new String[length];
int j = 0;
while (i.hasNext()) {
names[j] = (String) i.next();
j += 1;
}
return names;
}

Categories