2 * Copyright © 2016 Keith Packard <keithp@keithp.com>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19 package org.altusmetrum.altoslib_13;
25 import java.lang.reflect.*;
28 Writer quote(Writer writer, String a) throws IOException {
30 for (int i = 0; i < a.length(); i++) {
36 writer.append('\\').append(c);
50 Writer append(Writer result, AltosJson value, int indent, boolean pretty) throws IOException {
51 value.append(result, indent, pretty);
55 Writer append(Writer result, String string) throws IOException {
56 result.append(string);
60 Writer indent(Writer result, int indent) throws IOException {
62 for (int i = 0; i < indent; i++)
67 NumberFormat _nf_json;
69 NumberFormat nf_json() {
70 if (_nf_json == null) {
71 DecimalFormat nf = (DecimalFormat) NumberFormat.getNumberInstance(Locale.ROOT);
72 nf.setParseIntegerOnly(false);
73 nf.setGroupingUsed(false);
74 nf.setMaximumFractionDigits(17);
75 nf.setMinimumFractionDigits(0);
76 nf.setMinimumIntegerDigits(1);
77 nf.setDecimalSeparatorAlwaysShown(false);
84 class JsonHash extends JsonUtil {
85 Hashtable<String,AltosJson> hash;
87 void append_hash(Writer result, int indent, boolean pretty) throws IOException {
92 ArrayList<String> key_list = new ArrayList<String>(hash.keySet());
94 Collections.sort(key_list, new Comparator<String>() {
96 public int compare(String a, String b) { return a.compareTo(b); }
99 for (String key : key_list) {
100 AltosJson value = hash.get(key);
106 indent(result, indent+1);
108 append(result, ": ");
109 append(result, value, indent+1, pretty);
112 indent(result, indent);
116 void put(String key, AltosJson value) {
117 hash.put(key, value);
120 AltosJson get(String key) {
121 return hash.get(key);
125 hash = new Hashtable<String,AltosJson>();
129 class JsonArray extends JsonUtil {
130 ArrayList<AltosJson> array;
132 void append_array(Writer result, int indent, boolean pretty) throws IOException {
133 boolean first = true;
136 for (int i = 0; i < array.size(); i++) {
137 AltosJson value = array.get(i);
143 indent(result, indent+1);
144 append(result, value, indent+1, pretty);
147 indent(result, indent);
151 void put(int index, AltosJson value) {
152 if (index >= array.size())
153 array.add(index, value);
155 array.set(index, value);
158 AltosJson get(int index) {
159 if (index < 0 || index > array.size())
161 return array.get(index);
169 array = new ArrayList<AltosJson>();
180 static final int _string = 0;
181 static final int _double = 1;
182 static final int _long = 2;
183 static final int _boolean = 3;
184 static final int _oc = 4;
185 static final int _cc = 5;
186 static final int _os = 6;
187 static final int _cs = 7;
188 static final int _comma = 8;
189 static final int _colon = 9;
190 static final int _end = 10;
191 static final int _error = 11;
192 static final int _none = 12;
194 static String token_name(int token) {
225 String token_name() {
226 return token_name(token);
229 JsonToken(int token) {
233 JsonToken(int token, boolean bval) {
238 JsonToken(int token, double dval) {
243 JsonToken(int token, long lval) {
248 JsonToken(int token, String sval) {
253 JsonToken(int token, Writer bval) {
254 this(token, bval.toString());
261 class JsonLexer extends JsonUtil {
265 StringBuffer pending_token;
266 private JsonToken token;
268 static class keyword {
272 JsonToken match(String value) {
273 if (word.equals(value))
278 keyword(String word, JsonToken token) {
284 /* boolean values are the only keywords in json
286 static keyword[] keywords = {
287 new keyword("true", new JsonToken(JsonToken._boolean, true)),
288 new keyword("false", new JsonToken(JsonToken._boolean, false)),
289 new keyword("NegInfinity", new JsonToken(JsonToken._double, Double.NEGATIVE_INFINITY)),
290 new keyword("Infinity", new JsonToken(JsonToken._double, Double.POSITIVE_INFINITY)),
291 new keyword("NaN", new JsonToken(JsonToken._double, Double.NaN))
294 static JsonToken keyword(String word) {
295 for (int i = 0; i < keywords.length; i++) {
296 JsonToken token = keywords[i].match(word);
303 /* Get the next char (-1 for EOF) */
304 int ch() throws IOException {
312 pending_token.append((char) c);
320 throw new IllegalArgumentException("ungot buffer full");
321 pending_token.deleteCharAt( pending_token.length()-1);
327 String last_token_string() {
328 if (pending_token == null)
331 return pending_token.toString();
334 static boolean is_long_range(double d) {
335 return -9223372036854775808.0 <= d && d <= 9223372036854775807.0;
339 pending_token = new StringBuffer();
347 return new JsonToken(JsonToken._end);
353 return new JsonToken(JsonToken._oc);
355 return new JsonToken(JsonToken._cc);
357 return new JsonToken(JsonToken._os);
359 return new JsonToken(JsonToken._cs);
361 return new JsonToken(JsonToken._comma);
363 return new JsonToken(JsonToken._colon);
364 case '0': case '1': case '2': case '3': case '4':
365 case '5': case '6': case '7': case '8': case '9':
366 case '.': case '-': case '+':
367 StringBuffer dbuf = new StringBuffer();
368 boolean is_double = false;
369 while (Character.isDigit(c) || c == '.' || c == '+' || c == '-' || c == 'e' || c == 'E') {
370 if (c == '.' || c == 'E')
372 dbuf.appendCodePoint(c);
376 String dstr = dbuf.toString();
379 dval = nf_json().parse(dstr).doubleValue();
380 } catch (ParseException pe) {
381 return new JsonToken(JsonToken._error, dstr);
383 if (is_double || !is_long_range(dval))
384 return new JsonToken(JsonToken._double, dval);
386 long lval = Long.parseLong(dstr);
387 return new JsonToken(JsonToken._long, lval);
390 Writer bval = new StringWriter();
410 return new JsonToken(JsonToken._string, bval);
412 if (Character.isLetter(c)) {
413 StringBuffer tbuf = new StringBuffer();
415 tbuf.appendCodePoint(c);
417 } while (Character.isLetter(c));
419 JsonToken token = keyword(tbuf.toString());
426 } catch (IOException ie) {
427 return new JsonToken(JsonToken._error, "<EIO>");
441 JsonToken expect(int e) {
442 JsonToken t = token();
444 throw new IllegalArgumentException(String.format("got \"%s\" while expecting \"%s\"",
446 JsonToken.token_name(e)));
451 JsonLexer(String s) {
452 f = new AltosStringInputStream(s);
457 JsonLexer(InputStream f) {
465 * Parse a json string into a AltosJson object
470 void parse_error(String format, Object ... arguments) {
471 throw new IllegalArgumentException(String.format("line %d: JSON parse error %s\n",
473 String.format(format, arguments)));
476 /* Hashes are { string: value ... } */
478 JsonHash hash = new JsonHash();
480 /* skip the open brace */
483 /* Allow for empty hashes */
484 if (lexer.token().token == JsonToken._cc) {
490 String key = lexer.expect(JsonToken._string).sval;
491 lexer.expect(JsonToken._colon);
492 AltosJson value = value();
493 hash.put(key, value);
495 switch (lexer.token().token) {
496 case JsonToken._comma:
503 parse_error("got %s expect \",\" or \"}\"", lexer.token().token_name());
509 /* Arrays are [ value ... ] */
511 JsonArray array = new JsonArray();
514 for (int i = 0;; i++) {
515 /* Allow for empty arrays */
516 if (lexer.token().token == JsonToken._cs) {
521 AltosJson value = value();
523 switch (lexer.token().token) {
524 case JsonToken._comma:
531 parse_error("got %s expect \",\" or \"]\"", lexer.token().token_name());
537 /* Json is a simple LL language; one token is sufficient to
538 * identify the next object in the input
541 switch (lexer.token().token) {
543 return new AltosJson(hash());
545 return new AltosJson(array());
546 case JsonToken._double:
547 double dval = lexer.token().dval;
549 return new AltosJson(dval);
550 case JsonToken._long:
551 long lval = lexer.token().lval;
553 return new AltosJson(lval);
554 case JsonToken._string:
555 String sval = lexer.token().sval;
557 return new AltosJson(sval);
558 case JsonToken._boolean:
559 boolean bval = lexer.token().bval;
561 return new AltosJson(bval);
563 parse_error("Unexpected token \"%s\"", lexer.token().token_name());
573 JsonParse(String s) {
574 lexer = new JsonLexer(s);
577 JsonParse(InputStream f) {
578 lexer = new JsonLexer(f);
582 public class AltosJson extends JsonUtil {
583 private static final int type_none = 0;
584 private static final int type_hash = 1;
585 private static final int type_array = 2;
586 private static final int type_double = 3;
587 private static final int type_long = 4;
588 private static final int type_string = 5;
589 private static final int type_boolean = 6;
593 private JsonHash hash;
594 private JsonArray array;
595 private double d_number;
596 private long l_number;
597 private String string;
598 private boolean bool;
600 /* Generate string representation of the value
602 Writer append(Writer result, int indent, boolean pretty) throws IOException {
605 hash.append_hash(result, indent, pretty);
608 array.append_array(result, indent, pretty);
611 if (Double.isInfinite(d_number)) {
613 result.append("NegInfinity");
615 result.append("Infinity");
616 } else if (Double.isNaN(d_number)) {
617 result.append("NaN");
619 String dval = nf_json().format(d_number);
620 if (dval.equals("-0"))
626 result.append(Long.valueOf(l_number).toString());
629 quote(result, string);
632 result.append(bool ? "true" : "false");
638 private String toString(int indent, boolean pretty) {
640 Writer result = new StringWriter();
641 append(result, indent, pretty);
642 return result.toString();
643 } catch (Exception e) {
648 public String toString() {
649 return toString(0, false);
652 public String toPrettyString() {
653 return toString(0, true);
656 public void write(Writer w, int indent, boolean pretty) throws IOException {
657 append(w, indent, pretty);
660 public void write(Writer w) throws IOException {
664 /* Parse string representation to a value
667 public static AltosJson fromString(String string) {
668 JsonParse parse = new JsonParse(string);
670 return parse.parse();
671 } catch (IllegalArgumentException ie) {
672 System.out.printf("json:\n%s\n%s\n", string, ie.getMessage());
677 public static AltosJson fromInputStream(InputStream f) {
678 JsonParse parse = new JsonParse(f);
680 return parse.parse();
681 } catch (IllegalArgumentException ie) {
682 System.out.printf("json:\n%s\n", ie.getMessage());
687 /* Accessor functions
689 private boolean assert_type(boolean setting, int type, int other_type, String error) {
690 if (setting && this.type == type_none) {
694 if (this.type != type && this.type != other_type)
695 throw new IllegalArgumentException(error);
699 private boolean assert_type(boolean setting, int type, String error) {
700 return assert_type(setting, type, type, error);
703 private void assert_hash(boolean setting) {
704 if (!assert_type(setting, type_hash, "not a hash"))
705 hash = new JsonHash();
708 private void assert_array(boolean setting) {
709 if (!assert_type(setting, type_array, "not an array"))
710 array = new JsonArray();
713 private void assert_number() {
714 assert_type(false, type_double, type_long, "not a number");
717 private void assert_double() {
718 assert_type(true, type_double, type_long, "not a number");
721 private void assert_long() {
722 assert_type(true, type_long, type_double, "not a number");
725 private void assert_string(boolean setting) {
726 assert_type(setting, type_string, "not a string");
729 private void assert_boolean(boolean setting) {
730 assert_type(setting, type_boolean, "not a boolean");
733 /* Primitive accessors
735 public double number() {
737 if (type == type_double)
740 return (double) l_number;
743 public long l_number() {
745 if (type == type_double)
746 return (long) d_number;
751 public String string() {
752 assert_string(false);
756 public boolean bool() {
757 assert_boolean(false);
761 public AltosJson get(int index) {
763 return array.get(index);
766 public AltosJson get(String key) {
768 return hash.get(key);
776 /* Typed accessors with defaulting
778 public double get_double(String key, double def) {
779 AltosJson value = get(key);
781 return value.number();
786 public long get_long(String key, long def) {
787 AltosJson value = get(key);
789 return value.l_number();
793 public int get_int(String key, int def) {
794 AltosJson value = get(key);
796 return (int) value.l_number();
800 public String get_string(String key, String def) {
801 AltosJson value = get(key);
803 return value.string();
807 public boolean get_boolean(String key, boolean def) {
808 AltosJson value = get(key);
814 public double get_double(int index, double def) {
815 AltosJson value = get(index);
817 return value.number();
821 public long get_long(int index, long def) {
822 AltosJson value = get(index);
824 return value.l_number();
828 public int get_int(int index, int def) {
829 AltosJson value = get(index);
831 return (int) value.l_number();
835 public String get_string(int index, String def) {
836 AltosJson value = get(index);
838 return value.string();
842 public boolean get_boolean(int index, boolean def) {
843 AltosJson value = get(index);
849 public double[] get_double_array(String key, double[] def) {
850 AltosJson value = get(key);
852 double[] ret = new double[value.size()];
853 for (int i = 0; i < value.size(); i++)
854 ret[i] = value.get_double(i, def == null ? 0 : def[i]);
860 public int[] get_int_array(String key, int[] def) {
861 AltosJson value = get(key);
863 int[] ret = new int[value.size()];
864 for (int i = 0; i < value.size(); i++)
865 ret[i] = value.get_int(i, def == null ? 0 : def[i]);
871 /* Array setter functions
873 public AltosJson put(int index, AltosJson value) {
875 array.put(index, value);
879 public Object put(int index, Object value) {
882 array.put(index, new AltosJson(value));
886 public double put(int index, double value) {
888 array.put(index, new AltosJson(value));
892 public AltosJson put(int index, double[] value) {
895 array.put(index, new AltosJson(value));
900 public int[] put(int index, int[] value) {
903 array.put(index, new AltosJson(value));
908 public String put(int index, String value) {
911 array.put(index, new AltosJson(value));
916 public boolean put(int index, boolean value) {
918 array.put(index, new AltosJson(value));
922 /* Hash setter functions
924 public AltosJson put(String key, AltosJson value) {
926 hash.put(key, value);
930 public Object put(String key, Object value) {
933 hash.put(key, new AltosJson(value));
937 public double put(String key, double value) {
939 hash.put(key, new AltosJson(value));
943 public String put(String key, String value) {
946 hash.put(key, new AltosJson(value));
951 public boolean put(String key, boolean value) {
953 hash.put(key, new AltosJson(value));
957 public AltosJson[] put(String key, AltosJson[] value) {
960 hash.put(key, new AltosJson(value));
965 public double[] put(String key, double[] value) {
968 hash.put(key, new AltosJson(value));
973 public int[] put(String key, int[] value) {
976 hash.put(key, new AltosJson(value));
981 /* Primitive setter functions
983 public double put(double value) {
989 public byte put(byte value) {
995 public char put(char value) {
1001 public int put(int value) {
1007 public long put(long value) {
1013 public String put(String value) {
1014 assert_string(true);
1019 public boolean put(boolean value) {
1020 assert_boolean(true);
1025 private boolean isInnerClass(Class c) {
1026 for (Field field : c.getDeclaredFields())
1027 if (field.isSynthetic())
1032 /* Construct an object of the specified class from the JSON
1035 * This works as long as the structure is non-recursive, and
1036 * all inner classes are only members of their immediate outer
1039 @SuppressWarnings("unchecked")
1040 private Object make(Class c, Class enclosing_class, Object enclosing_object) {
1042 if (c == Boolean.TYPE) {
1044 } else if (c == Byte.TYPE) {
1045 ret = (Byte) (byte) l_number();
1046 } else if (c == Character.TYPE) {
1047 ret = (Character) (char) l_number();
1048 } else if (c == Integer.TYPE) {
1049 ret = (Integer) (int) l_number();
1050 } else if (c == Long.TYPE) {
1052 } else if (c == Double.TYPE) {
1054 } else if (c == String.class) {
1056 } else if (c.isArray()) {
1057 assert_array(false);
1059 Class element_class = c.getComponentType();
1060 if (element_class == Boolean.TYPE) {
1061 boolean[] array = (boolean[]) Array.newInstance(element_class, size());
1062 for (int i = 0; i < array.length; i++)
1063 array[i] = (Boolean) get(i).make(element_class);
1065 } else if (element_class == Byte.TYPE) {
1066 byte[] array = (byte[]) Array.newInstance(element_class, size());
1067 for (int i = 0; i < array.length; i++)
1068 array[i] = (Byte) get(i).make(element_class);
1070 } else if (element_class == Character.TYPE) {
1071 char[] array = (char[]) Array.newInstance(element_class, size());
1072 for (int i = 0; i < array.length; i++)
1073 array[i] = (Character) get(i).make(element_class);
1075 } else if (element_class == Integer.TYPE) {
1076 int[] array = (int[]) Array.newInstance(element_class, size());
1077 for (int i = 0; i < array.length; i++)
1078 array[i] = (Integer) get(i).make(element_class);
1080 } else if (element_class == Long.TYPE) {
1081 long[] array = (long[]) Array.newInstance(element_class, size());
1082 for (int i = 0; i < array.length; i++)
1083 array[i] = (Long) get(i).make(element_class);
1085 } else if (element_class == Double.TYPE) {
1086 double[] array = (double[]) Array.newInstance(element_class, size());
1087 for (int i = 0; i < array.length; i++)
1088 array[i] = (Double) get(i).make(element_class);
1091 Object[] array = (Object[]) Array.newInstance(element_class, size());
1092 for (int i = 0; i < array.length; i++)
1093 array[i] = get(i).make(element_class);
1098 Object object = null;
1100 /* Inner classes have a hidden extra parameter
1101 * to the constructor. Assume that the enclosing object is
1102 * of the enclosing class and construct the object
1105 if (enclosing_class != null && isInnerClass(c)) {
1106 Constructor<?> ctor = ((Class<?>)c).getDeclaredConstructor((Class<?>) enclosing_class);
1107 object = ctor.newInstance(enclosing_object);
1109 object = c.getDeclaredConstructor().newInstance();
1111 for (; c != Object.class; c = c.getSuperclass()) {
1112 for (Field field : c.getDeclaredFields()) {
1113 String fieldName = field.getName();
1114 Class fieldClass = field.getType();
1116 if (Modifier.isStatic(field.getModifiers()))
1118 if (field.isSynthetic())
1121 AltosJson json = get(fieldName);
1123 Object val = json.make(fieldClass, c, object);
1124 field.setAccessible(true);
1125 field.set(object, val);
1127 } catch (IllegalAccessException ie) {
1128 System.out.printf("%s:%s %s\n",
1129 c.getName(), fieldName, ie.toString());
1134 } catch (InvocationTargetException ie) {
1135 System.out.printf("%s: %s\n",
1136 c.getName(), ie.toString());
1138 } catch (NoSuchMethodException ie) {
1139 System.out.printf("%s: %s\n",
1140 c.getName(), ie.toString());
1142 } catch (InstantiationException ie) {
1143 System.out.printf("%s: %s\n",
1144 c.getName(), ie.toString());
1146 } catch (IllegalAccessException ie) {
1147 System.out.printf("%s: %s\n",
1148 c.getName(), ie.toString());
1155 /* This is the public API for the
1156 * above function which doesn't handle
1159 public Object make(Class c) {
1160 return make(c, null, null);
1163 /* Constructors, one for each primitive type, String and Object */
1164 public AltosJson(boolean bool) {
1165 type = type_boolean;
1169 public AltosJson(byte number) {
1171 this.l_number = number;
1174 public AltosJson(char number) {
1176 this.l_number = number;
1179 public AltosJson(int number) {
1181 this.l_number = number;
1184 public AltosJson(long number) {
1186 this.l_number = number;
1189 public AltosJson(double number) {
1191 this.d_number = number;
1194 public AltosJson(String string) {
1196 this.string = string;
1199 public AltosJson(Object object) {
1200 if (object instanceof Boolean) {
1201 type = type_boolean;
1202 bool = (Boolean) object;
1203 } else if (object instanceof Byte) {
1205 l_number = (Byte) object;
1206 } else if (object instanceof Character) {
1208 l_number = (Character) object;
1209 } else if (object instanceof Integer) {
1211 l_number = (Integer) object;
1212 } else if (object instanceof Long) {
1214 l_number = (Long) object;
1215 } else if (object instanceof Double) {
1217 d_number = (Double) object;
1218 } else if (object instanceof String) {
1220 string = (String) object;
1221 } else if (object.getClass().isArray()) {
1224 Class component_class = object.getClass().getComponentType();
1225 if (component_class == Boolean.TYPE) {
1226 boolean[] array = (boolean[]) object;
1227 for (int i = 0; i < array.length; i++)
1228 put(i, new AltosJson(array[i]));
1229 } else if (component_class == Byte.TYPE) {
1230 byte[] array = (byte[]) object;
1231 for (int i = 0; i < array.length; i++)
1232 put(i, new AltosJson(array[i]));
1233 } else if (component_class == Character.TYPE) {
1234 char[] array = (char[]) object;
1235 for (int i = 0; i < array.length; i++)
1236 put(i, new AltosJson(array[i]));
1237 } else if (component_class == Integer.TYPE) {
1238 int[] array = (int[]) object;
1239 for (int i = 0; i < array.length; i++)
1240 put(i, new AltosJson(array[i]));
1241 } else if (component_class == Long.TYPE) {
1242 long[] array = (long[]) object;
1243 for (int i = 0; i < array.length; i++)
1244 put(i, new AltosJson(array[i]));
1245 } else if (component_class == Double.TYPE) {
1246 double[] array = (double[]) object;
1247 for (int i = 0; i < array.length; i++)
1248 put(i, new AltosJson(array[i]));
1250 Object[] array = (Object[]) object;
1251 for (int i = 0; i < array.length; i++)
1252 put(i, new AltosJson(array[i]));
1256 for (Class c = object.getClass(); c != Object.class; c = c.getSuperclass()) {
1257 for (Field field : c.getDeclaredFields()) {
1258 String fieldName = field.getName();
1260 /* XXX hack to allow fields to be not converted */
1261 if (fieldName.startsWith("__"))
1264 /* Skip static fields */
1265 if (Modifier.isStatic(field.getModifiers()))
1268 /* Skip synthetic fields. We're assuming
1269 * those are always an inner class reference
1270 * to the outer class object
1272 if (field.isSynthetic())
1275 /* We may need to force the field to be accessible if
1278 field.setAccessible(true);
1279 Object val = field.get(object);
1281 AltosJson json = new AltosJson(val);
1282 put(fieldName, json);
1284 } catch (IllegalAccessException ie) {
1285 System.out.printf("%s:%s %s\n",
1286 c.getName(), fieldName, ie.toString());
1293 /* Array constructors, one for each primitive type, String and Object */
1294 public AltosJson(boolean[] bools) {
1296 for(int i = 0; i < bools.length; i++)
1297 put(i, new AltosJson(bools[i]));
1300 public AltosJson(byte[] numbers) {
1302 for(int i = 0; i < numbers.length; i++)
1303 put(i, new AltosJson(numbers[i]));
1306 public AltosJson(char[] numbers) {
1308 for(int i = 0; i < numbers.length; i++)
1309 put(i, new AltosJson(numbers[i]));
1312 public AltosJson(int[] numbers) {
1314 for(int i = 0; i < numbers.length; i++)
1315 put(i, new AltosJson(numbers[i]));
1318 public AltosJson(long[] numbers) {
1320 for(int i = 0; i < numbers.length; i++)
1321 put(i, new AltosJson(numbers[i]));
1324 public AltosJson(double[] numbers) {
1326 for(int i = 0; i < numbers.length; i++)
1327 put(i, new AltosJson(numbers[i]));
1330 public AltosJson(String[] strings) {
1332 for(int i = 0; i < strings.length; i++)
1333 put(i, new AltosJson(strings[i]));
1336 public AltosJson(AltosJson[] jsons) {
1338 for (int i = 0; i < jsons.length; i++)
1342 public AltosJson(Object[] array) {
1344 for (int i = 0; i < array.length; i++)
1345 put(i, new AltosJson(array[i]));
1348 /* Empty constructor
1350 public AltosJson() {
1354 public AltosJson(JsonHash hash) {
1359 public AltosJson(JsonArray array) {