Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions src/jsonlib/JSONParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package jsonlib;

import jsonlib.types.JSONDict;
import jsonlib.types.JSONList;
import jsonlib.types.JSONNumber;
import jsonlib.types.JSONObject;
import jsonlib.types.JSONString;

public class JSONParser {
private String content;
private int pos;

public JSONParser(String content) {
this.content = content;
this.pos = 0;
}

private char peek() {
if (pos >= 0 && pos < content.length())
return content.charAt(pos);
else
return '\0';
}

private char consume() {
char c = peek();

if (c != '\0')
pos++;

return c;
}

private void consumeWhitespace() {
while (peek() != '\0' && Character.isWhitespace(peek()))
consume();
}

private void expect(String needed) throws JSONExpectFailedException {
if (needed == null || needed.length() == 0)
return;

int i = 0;
while (peek() != '\0' && i < needed.length()) {
if (peek() != needed.charAt(i)) {
throw new JSONExpectFailedException(String.format("Expected '%c', but found '%c'", needed.charAt(i), peek()));
}

i++;
consume();
}

// it means we reached EOF before finishing the expected string
// so we should throw an exception
if (i < needed.length())
throw new JSONExpectFailedException(String.format("Expected '%c', but reached EOF", needed.charAt(i)));
}

private JSONString parseString() throws JSONExpectFailedException {
expect("\"");

StringBuilder sb = new StringBuilder();

while (peek() != '\0' && peek() != '"')
sb.append(consume());

expect("\"");

return new JSONString(sb.toString());
}

private JSONNumber parseNumber() throws JSONExpectFailedException {
int sign = 1;

if (peek() == '-') {
expect("-");
sign = -1;
}

StringBuilder integerPartStr = new StringBuilder();
StringBuilder floatingPartStr = new StringBuilder();
boolean isFloat = false;

while (peek() != '\0' && Character.isDigit(peek()))
integerPartStr.append(consume());

if (integerPartStr.length() == 0)
throw new JSONExpectFailedException(String.format("Expected digit but found '%c'", peek()));

if (peek() == '.') {
expect(".");
isFloat = true;
while (peek() != '\0' && Character.isDigit(peek()))
floatingPartStr.append(consume());

if (floatingPartStr.length() == 0)
throw new JSONExpectFailedException(String.format("Expected digit but found '%c'", peek()));
}

if (isFloat) {
double number = Double.valueOf(integerPartStr + "." + floatingPartStr);
return new JSONNumber<Double>(sign * number);
} else {
int number = Integer.valueOf(integerPartStr.toString());
return new JSONNumber<Integer>(sign * number);
}
}

private JSONList parseList() throws JSONExpectFailedException {
expect("[");
JSONList list = new JSONList();

while (peek() != '\0' && peek() != ']') {
JSONObject item = parse();
list.add(item);

consumeWhitespace();

if (peek() != ']')
expect(",");
}

expect("]");
return list;
}

private JSONDict parseDict() throws JSONExpectFailedException {
expect("{");
JSONDict dict = new JSONDict();

while (peek() != '\0' && peek() != '}') {
consumeWhitespace();
JSONString key = parseString();
consumeWhitespace();
expect(":");
JSONObject value = parse();

dict.put(key, value);

consumeWhitespace();
if (peek() != '}')
expect(",");
}

expect("}");
return dict;
}

public JSONObject parse() throws JSONExpectFailedException {
while (peek() != '\0') {
switch (peek()) {
case '[':
return parseList();
case '{':
return parseDict();
case '"':
return parseString();
default:
if (Character.isDigit(peek()) || peek() == '-')
return parseNumber();
else if (Character.isWhitespace(peek()))
consume();
else
// TODO: handle invalid characters properly.
throw new UnsupportedOperationException("not implemented yet");

// TODO: handle json boolean and null
break;
}
}
return null;
}

}

class JSONExpectFailedException extends Exception {

public JSONExpectFailedException(String msg) {
super(msg);
}
}
7 changes: 7 additions & 0 deletions src/jsonlib/JSONSerializable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package jsonlib;

import jsonlib.types.JSONObject;

public interface JSONSerializable {
public JSONObject serialize();
}
32 changes: 32 additions & 0 deletions src/jsonlib/JSONSerializableFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package jsonlib;

import jsonlib.types.JSONDict;

import java.util.HashMap;
import java.util.Map;

public class JSONSerializableFactory {
private static final Map<String, Class<? extends JSONSerializable>> typeRegistry = new HashMap<>();

public static void registerType(String type, Class<? extends JSONSerializable> clazz) {
typeRegistry.put(type, clazz);
}

public static JSONSerializable deserialize(JSONDict json) {
if (json == null) {
throw new IllegalArgumentException("Invalid JSON");
}

String type = json.getString("class");
Class<? extends JSONSerializable> clazz = typeRegistry.get(type);
if (clazz == null)
throw new IllegalArgumentException("Unknown type: " + type);

try {
java.lang.reflect.Method deserializeMethod = clazz.getMethod("deserialize", JSONDict.class);
return (JSONSerializable) deserializeMethod.invoke(null, json);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to deserialize: " + type, e);
}
}
}
74 changes: 74 additions & 0 deletions src/jsonlib/types/JSONDict.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package jsonlib.types;

import jsonlib.JSONSerializable;

import java.util.HashMap;
import java.util.Map;

public class JSONDict extends JSONObject {
public Map<JSONString, JSONObject> value;

public JSONDict() {
this.value = new HashMap<>();
}

public <T extends JSONSerializable> JSONDict(Map<String, T> value) {
this.value = new HashMap<>();

if (value == null)
return;

for (Map.Entry<String, T> entry : value.entrySet()) {
this.value.put(new JSONString(entry.getKey()), entry.getValue().serialize());
}
}

public void put(JSONString key, JSONObject value) {
this.value.put(key, value);
}

public void put(String key, JSONObject value) {
this.value.put(new JSONString(key), value);
}

public JSONObject get(String key) {
return this.value.get(new JSONString(key));
}

public String getString(String key) {
JSONObject obj = this.get(key);
if (!(obj instanceof JSONString))
throw new IllegalArgumentException(String.format("dict[%s] is not an string", key));

JSONString jsonString = (JSONString) obj;
return jsonString.getValue();
}

public int getInteger(String key) {
JSONObject obj = this.get(key);
if (!(obj instanceof JSONNumber))
throw new IllegalArgumentException(String.format("dict[%s] is not a number", key));

JSONNumber<Integer> jsonNumber = (JSONNumber<Integer>) obj;
return jsonNumber.getValue();
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{");

for (Map.Entry<JSONString, JSONObject> entry : value.entrySet()) {
sb.append(entry.getKey().toString());
sb.append(": ");
sb.append(entry.getValue().toString());
sb.append(", ");
}

// ignore the last ", "
sb.delete(sb.length() - 2, sb.length());

sb.append("}");
return sb.toString();
}
}
54 changes: 54 additions & 0 deletions src/jsonlib/types/JSONList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package jsonlib.types;

import jsonlib.JSONSerializable;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class JSONList extends JSONObject implements Iterable<JSONObject> {
public List<JSONObject> value;

public JSONList() {
this.value = new ArrayList<>();
}

public <T extends JSONSerializable> JSONList(List<T> value) {
this.value = new ArrayList<>();

if (value == null)
return;

for (T item : value) {
this.value.add(item.serialize());
}
}

public void add(JSONObject data) {
this.value.add(data);
}

public <T extends JSONSerializable> void add(T data) {
this.value.add(data.serialize());
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();

sb.append("[");
for (int i = 0; i < value.size(); i++) {
sb.append(value.get(i).toString());
if (i + 1 < value.size())
sb.append(", ");
}
sb.append("]");

return sb.toString();
}

@Override
public Iterator<JSONObject> iterator() {
return value.iterator();
}
}
18 changes: 18 additions & 0 deletions src/jsonlib/types/JSONNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package jsonlib.types;

public class JSONNumber<T extends Number> extends JSONObject {
public T value;

public JSONNumber(T value) {
this.value = value;
}

@Override
public String toString() {
return this.value.toString();
}

public T getValue() {
return this.value;
}
}
27 changes: 27 additions & 0 deletions src/jsonlib/types/JSONObject.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package jsonlib.types;

import jsonlib.JSONSerializable;

import java.util.List;
import java.util.Map;

public abstract class JSONObject {
public static JSONObject fromString(String data) {
return new JSONString(data);
}

public static <T extends Number> JSONObject fromNumber(T data) {
return new JSONNumber<T>(data);
}

public static <T extends JSONSerializable> JSONObject fromList(List<T> data) {
return new JSONList(data);
}

public static <T extends JSONSerializable> JSONObject fromDict(Map<String, T> data) {
return new JSONDict(data);
}

@Override
public abstract String toString();
}
Loading