Skip to content

Instantly share code, notes, and snippets.

@da1nerd
Last active February 23, 2017 17:54
Show Gist options
  • Save da1nerd/bb274330b911801a6e210df670bf3ecf to your computer and use it in GitHub Desktop.
Save da1nerd/bb274330b911801a6e210df670bf3ecf to your computer and use it in GitHub Desktop.
A utility class for reading Java Maps without going insane.
/**
* A utility for reading nested objects without pulling your hair out.
*
* Supported objects: List, Map, JSONObject, JSONArray
*
*/
public class ObjectReader implements Iterable<ObjectReader> {
private final Object map;
/**
* The map or value
* @param obj the object to be read.
*/
public ObjectReader(Object obj) {
this.map = obj;
}
/**
* Resolves a new instance of the reader with the value.
*
* @param key the key to look up
* @return an instance of the reader with the value
*/
public ObjectReader get(Object key) {
if(this.map instanceof Map && ((Map)this.map).containsKey(key)) {
// map
return new ObjectReader(((Map) this.map).get(key));
} else if (key instanceof String
&& this.map instanceof JSONObject
&& ((JSONObject) this.map).has((String)key)) {
// json object
Object value = null;
try {
value = ((JSONObject) this.map).get((String)key);
} catch (JSONException e) {
e.printStackTrace();
}
return new ObjectReader(value);
} else if (key instanceof Integer
&& this.map instanceof JSONArray
&& (Integer) key >= 0
&& ((JSONArray)this.map).length() > (Integer)key) {
// json array
Object value = null;
try {
value = ((JSONArray) this.map).get((Integer) key);
} catch (JSONException e) {
e.printStackTrace();
}
return new ObjectReader(value);
} else if (key instanceof Integer
&& this.map instanceof List
&& (Integer) key >= 0
&& ((List)this.map).size() > (Integer)key) {
// list
return new ObjectReader(((List)this.map).get((Integer)key));
} else {
return new ObjectReader(null);
}
}
/**
* Returns the keys in the reader
*
* @return a list of keys
*/
public List<Object> keys() {
if(this.map instanceof Map) {
return new ArrayList<Object>(((Map) this.map).keySet());
} else if(this.map instanceof Collection) {
int size = ((Collection) this.map).size();
List<Object> keys = new ArrayList<>();
for(int i = 0; i < size; i ++) {
keys.add(i);
}
return keys;
} else if(this.map instanceof JSONArray) {
int size = ((JSONArray) this.map).length();
List<Object> keys = new ArrayList<>();
for(int i = 0; i < size; i ++) {
keys.add(i);
}
return keys;
} else if(this.map instanceof JSONObject) {
List<Object> keys = new ArrayList<>();
Iterator<String> iter = ((JSONObject) this.map).keys();
while(iter.hasNext()) keys.add(iter.next());
return keys;
} else {
return new ArrayList<>();
}
}
/**
* Returns the value of the reader
* @return the value
*/
public Object value() {
if(this.map != null) return this.map;
return null;
}
/**
* Returns the size of the object being read if it is a map or collection.
* If the reader contains anything else this will return 0.
*
* @return the size of the map
*/
public int size() {
if(this.map instanceof Map) {
return ((Map) this.map).size();
} else if(this.map instanceof Collection) {
return ((Collection) this.map).size();
} else if(this.map instanceof JSONArray) {
return ((JSONArray) this.map).length();
} else if(this.map instanceof JSONObject) {
return ((JSONObject) this.map).length();
} else {
return 0;
}
}
/**
* Convenience method for checking of the value/map is null
* @return true if the value or map is null
*/
public boolean isNull() {
return this.map == null;
}
/**
* Returns the string value of the reader.
* An empty string will be returned if the reader value is null;
* @return the string value
*/
@Override
public String toString() {
if(this.map != null) return this.map.toString();
return "";
}
@Override
public Iterator<ObjectReader> iterator() {
return new ObjectIterator(this);
}
/**
* Utility class for iterating over a reader
*/
class ObjectIterator implements Iterator<ObjectReader> {
private final ObjectReader reader;
private final List<Object> keys;
private int index = 0;
private ObjectIterator(ObjectReader reader) {
this.reader = reader;
this.keys = reader.keys();
}
@Override
public boolean hasNext() {
return this.index < this.keys.size();
}
@Override
public ObjectReader next() {
return this.reader.get(this.keys.get(this.index ++));
}
@Override
public void remove() {
throw new UnsupportedOperationException("not supported yet");
}
}
}
@da1nerd
Copy link
Author

da1nerd commented Jan 26, 2017

The Situation

You have some complex nested object. And you know what the expected structure is.

The Problem

Normally when dealing with maps, lists, and JSON you must do all sorts ridiculous things like

  • null check
  • instanceof checks
  • type checking
  • casting
  • nested conditionals

This leads to unreadable code, frustration, insanity, and the end of the civilized world.

The Solution

Now you can do things like this:

ObjectReader reader = new ObjectReader(myMap);
String value = (String)reader.get("first_key").get("second_key").get("n_key").value();

What About Lists and JSON?

The reader does support lists and json objects/arrays even though I initially built this for maps. To access an item in a list just pass in an integer.

Here we are retrieving the string value from a triply nested list (if you're into that sort of thing):

ObjectReader reader = new ObjectReader(myList);
String value = (String)reader.get(0).get(1).get(2).value();

Also, it's completely safe to mix up retrieving by key and index.

ObjectReader reader = new ObjectReader(myList);
String value = (String)reader.get(0).get("some_key").get(2).value();

Since JSON arrays or objects keyed/indexed by strings/integers you can access them just like the examples above.

What About Type Safety?

In my situation I'm working with nested maps of varying structure so it's impossible to support type safety using generics.
Le sigh.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment