Skip to content

Instantly share code, notes, and snippets.

@gimenete
Created August 5, 2011 13:37
Show Gist options
  • Save gimenete/1127552 to your computer and use it in GitHub Desktop.
Save gimenete/1127552 to your computer and use it in GitHub Desktop.
Optimized Json simple parser / generator. Works in Android
/*
* Copyright 2009-2011 Alberto Gimeno <gimenete at gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class Json implements Iterable<Json> {
private Object object;
private Map<String, Json> map;
private List<Json> list;
public Json(Object object) {
this.object = object;
if(object == null) return;
if(object instanceof Number) return;
if(object instanceof String) return;
if(object instanceof Boolean) return;
throw new IllegalArgumentException("Unsupported type: " + object.getClass().getName());
}
private Json() {
}
public static Json map() {
Json json = new Json();
json.map = new HashMap<String, Json>();
return json;
}
public static Json sortedMap() {
Json json = new Json();
json.map = new TreeMap<String, Json>();
return json;
}
public static Json list(Object...objects) {
Json json = new Json();
json.list = new ArrayList<Json>();
json.add(objects);
return json;
}
private Json wrap(Object object) {
if(object instanceof Json)
return (Json) object;
return new Json(object);
}
public Json add(Object...objects) {
for (Object object : objects) {
list.add(wrap(object));
}
return this;
}
public Json addAt(int index, Object object) {
list.add(index, wrap(object));
return this;
}
public Json removeAt(int index) {
list.remove(index);
return this;
}
public int indexOf(Object value) {
return list.indexOf(wrap(value));
}
public Json get(String key) {
return map.get(key);
}
public Json put(String key, Object value) {
map.put(key, wrap(value));
return this;
}
public Json putAll(Json json) {
Set<String> keys = json.keys();
for (String key : keys) {
put(key, json.get(key));
}
return this;
}
public Json addAll(Json json) {
for (Json js : json) {
add(js);
}
return this;
}
public Json removeAll(Json json) {
if(json.isList()) {
for (Json js : json) {
remove(js);
}
} else if(json.isMap()) {
for (String key : json.keys()) {
remove(key);
}
}
return this;
}
public Json at(int index) {
// it prevents from generating any exception in case the model has changed dynamically
// by adding fields to the json embedded list
if(index >= list.size())
return new Json();
return list.get(index);
}
public Set<String> keys() {
return map.keySet();
}
public boolean containsKey(String key) {
return map.containsKey(key);
}
public boolean containsValue(Object obj) {
return map.containsValue(wrap(obj));
}
public boolean contains(Object obj) {
return list.contains(wrap(obj));
}
public Collection<Json> values() {
return map.values();
}
public boolean remove(Object obj) {
if(list != null) return list.remove(wrap(obj));
if(map != null) return map.remove(obj) != null;
return false;
}
public void sumIntegers(Json other) {
for (String key : other.keys()) {
Json value = get(key);
if(value == null) {
put(key, other.get(key));
} else {
put(key, value.asLong()+other.get(key).asLong());
}
}
}
public void sumReals(Json other) {
for (String key : other.keys()) {
Json value = get(key);
if(value == null) {
put(key, other.get(key));
} else {
put(key, value.asDouble()+other.get(key).asDouble());
}
}
}
public void sumInteger(String key, long value) {
if(containsKey(key)) {
value += get(key).asLong();
}
put(key, value);
}
public void sumReal(String key, double value) {
if(containsKey(key)) {
value += get(key).asDouble();
}
put(key, value);
}
public void putDefault(String key, Object value) {
if(!containsKey(key))
put(key, value);
}
public int size() {
if(map != null) return map.size();
if(list != null) return list.size();
return 1;
}
public Iterator<Json> iterator() {
if(list != null) return list.iterator();
if(map != null) return map.values().iterator();
return null;
}
public boolean isEmpty() {
return size() == 0;
}
private void formatString(Writer writer, String s) throws IOException {
writer.write('\"');
for(int i=0; i<s.length(); i++) {
char c = s.charAt(i);
switch(c) {
case '\\':
case '\"':
case '/':
writer.write('\\');
writer.write(c);
break;
case '\b':
writer.write("\\b");
break;
case '\f':
writer.write("\\f");
break;
case '\n':
writer.write("\\n");
break;
case '\r':
writer.write("\\r");
break;
case '\t':
writer.write("\\t");
break;
default:
writer.write(c);
}
}
writer.write("\"");
}
public void format(Writer writer, Object o) throws IOException {
if(o instanceof String)
formatString(writer, (String) o);
else
writer.write(o.toString());
}
public String toString() {
StringWriter writer = new StringWriter();
try {
write(writer);
} catch (IOException e) {
// Should never happen
throw new SienaException(e);
}
return writer.toString();
}
public void write(Writer writer) throws IOException {
if(object != null) {
format(writer, object);
return;
}
if(map != null) {
if(map.isEmpty()) {
writer.write("{}");
return;
}
writer.write("{");
boolean first = true;
for (Map.Entry<String, Json> entry : map.entrySet()) {
if(!first) {
writer.write(", ");
} else {
first = false;
}
formatString(writer, entry.getKey());
writer.write(": ");
entry.getValue().write(writer);
}
writer.write("}");
return;
}
if(list != null) {
if(list.isEmpty()) {
writer.write("[]");
return;
}
writer.write("[");
boolean first = true;
for (Json obj : list) {
if(!first) {
writer.write(", ");
} else {
first = false;
}
obj.write(writer);
}
writer.write("]");
return;
}
writer.write("null");
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((list == null) ? 0 : list.hashCode());
result = prime * result + ((map == null) ? 0 : map.hashCode());
result = prime * result + ((object == null) ? 0 : object.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Json other = (Json) obj;
if (list == null) {
if (other.list != null)
return false;
} else if (!list.equals(other.list))
return false;
if (map == null) {
if (other.map != null)
return false;
} else if (!map.equals(other.map))
return false;
if (object == null) {
if (other.object != null)
return false;
} else if (!object.equals(other.object))
return false;
return true;
}
public boolean equalsTo(Json other) {
if(other == null) return false;
if(this == other) return true;
if(object != null) {
if(isNumber()) {
if(!other.isNumber()) return false;
return Double.doubleToLongBits(asDouble()) == Double.doubleToLongBits(other.asDouble());
}
return object.equals(other.object);
}
if(list != null) {
if(other.list == null) return false;
int size = list.size();
if(other.list.size() != size) return false;
while(size-- > 0) {
if(!list.get(size).equalsTo(other.list.get(size)))
return false;
}
} else if(map != null) {
if(other.map == null) return false;
int size = map.size();
if(other.map.size() != size) return false;
for (String key : map.keySet()) {
Json value = map.get(key);
if(!value.equalsTo(other.map.get(key)))
return false;
}
}
return true;
}
public String str() {
return asString();
}
public String asString() {
if(object == null) return null;
return object.toString();
}
public Date asDate() {
if(object == null) return null;
return (Date)object;
}
public boolean bool() {
return asBoolean();
}
public boolean asBoolean() {
if(object == null) return false;
return ((Boolean) object).booleanValue();
}
public int asInt() {
if(object == null) return 0;
return ((Number) object).intValue();
}
public short asShort() {
if(object == null) return 0;
return ((Number) object).shortValue();
}
public byte asByte() {
if(object == null) return 0;
return ((Number) object).byteValue();
}
public long asLong() {
if(object == null) return 0;
return ((Number) object).longValue();
}
public Double asDouble() {
if(object == null) return 0d;
return ((Number) object).doubleValue();
}
public float asFloat() {
if(object == null) return 0f;
return ((Number) object).floatValue();
}
public boolean isNull() {
return object == null && list == null && map == null;
}
public boolean isNumber() {
return object != null && object instanceof Number;
}
public boolean isBoolean() {
return object != null && object instanceof Boolean;
}
public boolean isString() {
return object != null && object instanceof String;
}
public boolean isDate() {
return object != null && object instanceof Date;
}
public boolean isMap() {
return map != null;
}
public boolean isList() {
return list != null;
}
public Json find(Object...params) {
Json data = this;
for (Object object : params) {
if(object instanceof String && data.isMap()) {
data = data.get((String) object);
if(data == null) return null;
} else if(object instanceof Integer && data.isList()) {
int value = ((Integer) object).intValue();
if(value >= data.size()) return null;
data = data.at(value);
} else {
return null;
}
}
return data;
}
public static Json loads(String s) {
return new Parser(s).parse();
}
} class Parser {
private int n;
private char[] s;
private char c;
private char[] buff;
private int i;
public Parser(String str) {
this.s = str.toCharArray();
buff = new char[s.length];
}
public Json parse() {
c = s[n++];
return next();
}
private Json next() {
if(c == '\"') {
i = 0;
do {
c = s[n++];
if(c == '\\') {
c = s[n++];
switch(c) {
case 'b':
buff[i++] = '\b';
continue;
case 'f':
buff[i++] = '\f';
continue;
case 'n':
buff[i++] = '\n';
continue;
case 'r':
buff[i++] = '\r';
continue;
case 't':
buff[i++] = '\t';
continue;
case '\"':
case '\\':
case '/':
buff[i++] = c;
continue;
case 'u':
char u = (char) (Integer.parseInt(
new String(s, n, 4), 16));
n+=4;
c = s[n];
buff[i++] = u;
continue;
default:
throw new SienaException("Invalid escape character: \\"+c+" at position "+n);
}
} else if(c == '\"') {
c = s[n++];
return new Json(new String(buff, 0, i));
} // else
buff[i++] = c;
} while(true);
} else if(c == '[') {
Json result = Json.list();
c = s[n++];
while(c == ' ' || c == '\t' || c == '\n' || c == '\r') { c = s[n++]; }
if(c == ']') {
if(n != s.length) c = s[n++];
return result;
}
do {
result.add(next());
while(c == ' ' || c == '\t' || c == '\n' || c == '\r') { c = s[n++]; }
if(c == ']') {
if(n != s.length) c = s[n++];
return result;
}
if(c != ',') throw new SienaException("expected ',' or ']' at character "+n);
c = s[n++];
while(c == ' ' || c == '\t' || c == '\n' || c == '\r') { c = s[n++]; }
} while(true);
} else if(c == '{') {
Json result = Json.map();
c = s[n++];
while(c == ' ' || c == '\t' || c == '\n' || c == '\r') { c = s[n++]; }
if(c == '}') {
if(n != s.length) c = s[n++];
return result;
}
do {
Json k = next();
if(!k.isString())
throw new SienaException("find non-string key at character "+n);
String key = k.str();
while(c == ' ' || c == '\t' || c == '\n' || c == '\r') { c = s[n++]; }
if(c != ':') throw new SienaException("expected ':' at character "+n);
c = s[n++];
while(c == ' ' || c == '\t' || c == '\n' || c == '\r') { c = s[n++]; }
Json value = next();
result.put(key, value);
while(c == ' ' || c == '\t' || c == '\n' || c == '\r') { c = s[n++]; }
if(c == '}') {
if(n != s.length) c = s[n++];
return result;
}
if(c != ',') throw new SienaException("expected: ',' or '}'' at character "+n);
c = s[n++];
while(c == ' ' || c == '\t' || c == '\n' || c == '\r') { c = s[n++]; }
} while(true);
} else if(c == 't') {
if(s[n++] != 'r' || s[n++] != 'u' || s[n++] != 'e')
throw new SienaException("expected 'true' at character "+n);
if(n != s.length) c = s[n++];
return new Json(Boolean.TRUE);
} else if(c == 'f') {
if(s[n++] != 'a' || s[n++] != 'l' || s[n++] != 's' || s[n++] != 'e')
throw new SienaException("expected 'false' at character "+n);
if(n != s.length) c = s[n++];
return new Json(Boolean.FALSE);
} else if(c == 'n') {
if(s[n++] != 'u' || s[n++] != 'l' || s[n++] != 'l')
throw new SienaException("expected 'true' at character "+n);
if(n != s.length) c = s[n++];
return new Json(null);
} else if(c == '-' || (c >= '0' && c <= '9')) {
i = 0;
boolean d = false;
do {
buff[i++] = c;
c = s[n++];
if(c == '.' || c == 'e' || c == 'E') d = true;
} while((c >= '0' && c <= '9') || c == 'e' ||
c == 'E' || c == '.' || c == '+' || c == '-');
if(d)
return new Json(Double.parseDouble(new String(buff, 0, i)));
return new Json(Long.parseLong(new String(buff, 0, i)));
} else {
throw new SienaException("expected: '{', '[', '\"', true, false, null, or a number at "+n);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment