package foundation.stack.datamill.json; import foundation.stack.datamill.reflection.Member; import foundation.stack.datamill.serialization.*; import foundation.stack.datamill.values.*; import foundation.stack.datamill.reflection.impl.TripleArgumentTypeSwitch; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import rx.functions.Action1; import java.time.LocalDateTime; import java.util.*; import java.util.function.Function; /** * @author Ravi Chodavarapu ([email protected]) */ public class JsonObject implements Json, ReflectableValue, StructuredOutput<JsonObject>, DeepStructuredInput { private static final TripleArgumentTypeSwitch<JSONObject, String, JsonProperty, Object> propertyAsObjectSwitch = new TripleArgumentTypeSwitch<JSONObject, String, JsonProperty, Object>() { @Override protected Object caseBoolean(JSONObject value1, String value2, JsonProperty value3) { return value1.has(value2) && !value1.isNull(value2) ? value3.asBoolean() : null; } @Override protected Object caseByte(JSONObject value1, String value2, JsonProperty value3) { return value1.has(value2) && !value1.isNull(value2) ? value3.asByte() : null; } @Override protected Object caseCharacter(JSONObject value1, String value2, JsonProperty value3) { return value1.has(value2) && !value1.isNull(value2) ? value3.asCharacter() : null; } @Override protected Object caseShort(JSONObject value1, String value2, JsonProperty value3) { try { return value1.has(value2) && !value1.isNull(value2) ? value3.asShort() : null; } catch (JSONException __) { return null; } } @Override protected Object caseInteger(JSONObject value1, String value2, JsonProperty value3) { try { return value1.has(value2) && !value1.isNull(value2) ? value3.asInteger() : null; } catch (JSONException __) { return null; } } @Override protected Object caseLong(JSONObject value1, String value2, JsonProperty value3) { try { return value1.has(value2) && !value1.isNull(value2) ? value3.asLong() : null; } catch (JSONException __) { return null; } } @Override protected Object caseFloat(JSONObject value1, String value2, JsonProperty value3) { return value1.has(value2) && !value1.isNull(value2) ? value3.asFloat() : null; } @Override protected Object caseDouble(JSONObject value1, String value2, JsonProperty value3) { return value1.has(value2) && !value1.isNull(value2) ? value3.asDouble() : null; } @Override protected Object caseLocalDateTime(JSONObject value1, String value2, JsonProperty value3) { return value1.has(value2) ? value3.asLocalDateTime() : null; } @Override protected Object caseByteArray(JSONObject value1, String value2, JsonProperty value3) { return value1.has(value2) ? value3.asByteArray() : null; } @Override protected Object defaultCase(JSONObject value1, String value2, JsonProperty value3) { return value3.asJson(); } }; final JSONObject object; JsonObject(JSONObject object) { this.object = object; } public JsonObject() { object = new JSONObject(); } public JsonObject(String json) { object = new JSONObject(json); } public JsonObject(Map<String, Object> values) { object = new JSONObject(values); } @Override public boolean asBoolean() { throw new JsonException("A JSON object cannot be converted to a boolean!"); } @Override public byte asByte() { throw new JsonException("A JSON object cannot be converted to a byte!"); } @Override public byte[] asByteArray() { return asString().getBytes(); } @Override public char asCharacter() { throw new JsonException("A JSON object cannot be converted to a character!"); } @Override public float asFloat() { throw new JsonException("A JSON object cannot be converted to a float!"); } @Override public int asInteger() { throw new JsonException("A JSON object cannot be converted to an integer!"); } @Override public double asDouble() { throw new JsonException("A JSON object cannot be converted to a double!"); } @Override public LocalDateTime asLocalDateTime() { throw new JsonException("A JSON object cannot be converted to a LocalDateTime!"); } @Override public long asLong() { throw new JsonException("A JSON object cannot be converted to a long!"); } @Override public Object asObject(Class<?> type) { if (type == String.class) { return asString(); } return this; } @Override public short asShort() { throw new JsonException("A JSON object cannot be converted to a short!"); } @Override public String asString() { return object.toString(); } @Override public DeepStructuredInput forEach(String name, Action1<Value> action) { Object child = object.opt(name); if (child instanceof JSONArray) { JSONArray array = (JSONArray) child; for (int i = 0; i < array.length(); i++) { action.call(new JsonElement(array, i)); } } else { action.call(new JsonProperty(name)); } return this; } @Override public <T> DeepStructuredInput forEach(String name, DeserializationStrategy<T> strategy, Action1<T> action) { JSONObject child = object.optJSONObject(name); if (child != null) { T deserialized = strategy.deserialize(new JsonObject(child)); action.call(deserialized); } else { JSONArray array = object.optJSONArray(name); if (array != null) { for (int i = 0; i < array.length(); i++) { T deserialized = strategy.deserialize(new JsonObject(array.getJSONObject(i))); action.call(deserialized); } } } return this; } @Override public Value get(String property) { return new JsonProperty(property); } @Override public Value get(Member member) { return get(member.name()); } @Override public <T> T get(String name, DeserializationStrategy<T> strategy) { JSONObject child = object.optJSONObject(name); if (child != null) { return strategy.deserialize(new JsonObject(child)); } return null; } @Override public boolean isBoolean() { return false; } @Override public boolean isByte() { return false; } @Override public boolean isCharacter() { return false; } @Override public boolean isDouble() { return false; } @Override public boolean isFloat() { return false; } @Override public boolean isInteger() { return false; } @Override public boolean isLong() { return false; } @Override public boolean isNumeric() { return false; } @Override public boolean isShort() { return false; } @Override public boolean isString() { return false; } @Override public <T> T map(Function<Value, T> mapper) { return mapper.apply(this); } @Override public <T> JsonObject put(String key, T value) { object.put(key, value); return this; } @Override public <T> JsonObject put(String key, T value, SerializationStrategy<T> strategy) { if (value != null) { JsonObject serialized = new JsonObject(); serialized = (JsonObject) strategy.serialize(serialized, value); if (serialized != null) { object.put(key, serialized.object); } } return this; } @Override public <T> JsonObject put(String key, Iterable<T> values, SerializationStrategy<T> strategy) { if (values != null) { JSONArray array = new JSONArray(); for (T value : values) { JsonObject serialized = new JsonObject(); serialized = (JsonObject) strategy.serialize(serialized, value); if (serialized != null) { array.put(serialized.object); } else { array.put((Object) null); } } this.object.put(key, array); } return this; } public JsonObject put(String key, JsonObject object) { this.object.put(key, object.object); return this; } public JsonObject put(String key, JsonArray array) { object.put(key, array.array); return this; } @Override public JsonObject put(String name, Object[] value) { object.put(name, Arrays.asList(value)); return this; } @Override public JsonObject put(String name, Map<String, ?> value) { object.put(name, value); return this; } public Set<String> propertyNames() { return object.keySet(); } @Override public String toString() { return asString(); } private class JsonElement implements Value { private final JSONArray array; private final int index; private JsonElement(JSONArray array, int index) { this.array = array; this.index = index; } @Override public boolean asBoolean() { return array.getBoolean(index); } @Override public byte asByte() { return (byte) array.getInt(index); } @Override public byte[] asByteArray() { try { JSONArray array = this.array.getJSONArray(index); if (array != null) { byte[] bytes = new byte[array.length()]; for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) array.getInt(i); } return bytes; } } catch (JSONException e) { String value = asString(); if (value != null) { return value.getBytes(); } } return null; } @Override public char asCharacter() { try { return (char) array.getInt(index); } catch (JSONException e) { String value = array.getString(index); if (value.length() == 1) { return value.charAt(0); } throw new JsonException("Property cannot be converted to a character!"); } } @Override public double asDouble() { return array.getDouble(index); } @Override public float asFloat() { return (float) array.getDouble(index); } @Override public int asInteger() { return array.getInt(index); } @Override public LocalDateTime asLocalDateTime() { String value = array.optString(index); if (value != null) { return LocalDateTime.parse(value); } return null; } @Override public long asLong() { return array.getLong(index); } @Override public Object asObject(Class<?> type) { // return propertyAsObjectSwitch.doSwitch(type, object, name, this); return null; } @Override public short asShort() { return (short) array.getInt(index); } @Override public String asString() { return array.optString(index); } @Override public <T> T map(Function<Value, T> mapper) { return mapper.apply(this); } } public class JsonProperty implements Value { private String name; private JsonProperty(String name) { this.name = name; } @Override public boolean asBoolean() { return object.getBoolean(name); } @Override public byte asByte() { return (byte) object.getInt(name); } @Override public byte[] asByteArray() { try { JSONArray array = object.getJSONArray(name); if (array != null) { byte[] bytes = new byte[array.length()]; for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) array.getInt(i); } return bytes; } } catch (JSONException e) { String value = asString(); if (value != null) { return value.getBytes(); } } return null; } @Override public char asCharacter() { try { return (char) object.getInt(name); } catch (JSONException e) { String value = object.getString(name); if (value.length() == 1) { return value.charAt(0); } throw new JsonException("Property cannot be converted to a character!"); } } @Override public double asDouble() { return object.optDouble(name); } @Override public float asFloat() { return (float) object.optDouble(name); } @Override public int asInteger() { return object.optInt(name); } public JsonArray asJsonArray() { JSONArray array = object.optJSONArray(name); if (array != null) { return new JsonArray(array); } return null; } public JsonObject asJson() { JSONObject json = object.optJSONObject(name); if (json != null) { return new JsonObject(json); } return null; } @Override public LocalDateTime asLocalDateTime() { String value = object.optString(name); if (value != null) { return LocalDateTime.parse(value); } return null; } @Override public long asLong() { return object.optLong(name); } @Override public Object asObject(Class<?> type) { return propertyAsObjectSwitch.doSwitch(type, object, name, this); } @Override public short asShort() { return (short) (int) object.optInt(name); } @Override public String asString() { return object.optString(name); } @Override public <T> T map(Function<Value, T> mapper) { return mapper.apply(this); } } }