json.vala 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. /*
  2. * Copyright (c) 2012-2026 Daniele Bartolini et al.
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. /*
  6. * Original C# code:
  7. * Public Domain Niklas Frykholm
  8. */
  9. namespace Crown
  10. {
  11. public errordomain JsonSyntaxError
  12. {
  13. BAD_TOKEN,
  14. BAD_VALUE,
  15. BAD_STRING,
  16. BAD_COMMENT
  17. }
  18. public errordomain JsonWriteError
  19. {
  20. BAD_VALUE,
  21. FILE_OPEN,
  22. FILE_WRITE
  23. }
  24. /// <summary>
  25. /// Provides functions for encoding and decoding files in the JSON format.
  26. /// </summary>
  27. [Compact]
  28. public class JSON
  29. {
  30. /// <summary>
  31. /// Encodes the hashtable t in the JSON format. The hash table can
  32. /// contain, numbers, bools, strings, ArrayLists and Hashtables.
  33. /// </summary>
  34. public static string encode(Value? t) throws JsonWriteError
  35. {
  36. StringBuilder sb = new StringBuilder();
  37. write(t, sb, 0);
  38. sb.append_c('\n');
  39. return sb.str;
  40. }
  41. /// <summary>
  42. /// Decodes a JSON bytestream into a hash table with numbers, bools, strings,
  43. /// ArrayLists and Hashtables.
  44. /// </summary>
  45. public static Value? decode(uint8[] sjson) throws JsonSyntaxError
  46. {
  47. int index = 0;
  48. return parse(sjson, ref index);
  49. }
  50. /// <summary>
  51. /// Convenience function for loading a file.
  52. /// </summary>
  53. public static Hashtable load(string path) throws JsonSyntaxError
  54. {
  55. FileStream fs = FileStream.open(path, "rb");
  56. if (fs == null)
  57. return new Hashtable();
  58. // Get file size
  59. fs.seek(0, FileSeek.END);
  60. size_t size = fs.tell();
  61. fs.rewind();
  62. if (size == 0)
  63. return new Hashtable();
  64. // Read whole file
  65. uint8[] bytes = new uint8[size];
  66. size_t bytes_read = fs.read(bytes);
  67. if (bytes_read != size)
  68. return new Hashtable();
  69. return decode(bytes) as Hashtable;
  70. }
  71. /// <summary>
  72. /// Convenience function for saving a file.
  73. /// </summary>
  74. public static void save(Hashtable h, string path) throws JsonWriteError
  75. {
  76. FileStream fs = FileStream.open(path, "wb");
  77. if (fs == null)
  78. throw new JsonWriteError.FILE_OPEN("Unable to open '%s'".printf(path));
  79. uint8[] data = encode(h).data;
  80. size_t len = data.length;
  81. if (fs.write(data) != len)
  82. throw new JsonWriteError.FILE_WRITE("Error while writing '%s'".printf(path));
  83. }
  84. static void write_new_line(StringBuilder builder, int indentation)
  85. {
  86. if (builder.len > 0)
  87. builder.append_c('\n');
  88. for (int i = 0; i < indentation; ++i)
  89. builder.append_c('\t');
  90. }
  91. static void write(Value? o, StringBuilder builder, int indentation) throws JsonWriteError
  92. {
  93. if (o == null)
  94. builder.append("null");
  95. else if (o.holds(typeof(bool)) && (bool)o == false)
  96. builder.append("false");
  97. else if (o.holds(typeof(bool)))
  98. builder.append("true");
  99. else if (o.holds(typeof(int)))
  100. builder.append_printf("%d", (int)o);
  101. else if (o.holds(typeof(float)))
  102. builder.append_printf("%.9g", (float)o);
  103. else if (o.holds(typeof(double)))
  104. builder.append_printf("%.17g", (double)o);
  105. else if (o.holds(typeof(string)))
  106. write_string((string)o, builder);
  107. else if (o.holds(typeof(Gee.ArrayList)))
  108. write_array((Gee.ArrayList)o, builder, indentation);
  109. else if (o.holds(typeof(Hashtable)))
  110. write_object((Hashtable)o, builder, indentation);
  111. else
  112. throw new JsonWriteError.BAD_VALUE("Unsupported value type '%s'".printf(o.type_name()));
  113. }
  114. static void write_string(string s, StringBuilder builder)
  115. {
  116. builder.append_c('"');
  117. for (int i = 0; i < s.length; ++i) {
  118. uint8 c = s[i];
  119. if (c == '"' || c == '\\') {
  120. builder.append_c('\\');
  121. builder.append_c((char)c);
  122. } else if (c == '\n') {
  123. builder.append_c('\\');
  124. builder.append_c('n');
  125. } else {
  126. builder.append_c((char)c);
  127. }
  128. }
  129. builder.append_c('"');
  130. }
  131. static void write_array(Gee.ArrayList<Value?> a, StringBuilder builder, int indentation) throws JsonWriteError
  132. {
  133. bool write_comma = false;
  134. builder.append("[ ");
  135. foreach (Value? item in a) {
  136. if (write_comma)
  137. builder.append(", ");
  138. write(item, builder, indentation + 1);
  139. write_comma = true;
  140. }
  141. builder.append("]");
  142. }
  143. static void write_object(Hashtable t, StringBuilder builder, int indentation) throws JsonWriteError
  144. {
  145. builder.append_c('{');
  146. bool write_comma = false;
  147. foreach (var de in t.entries) {
  148. if (write_comma)
  149. builder.append(", ");
  150. write_new_line(builder, indentation);
  151. write(de.key, builder, indentation);
  152. builder.append(" : ");
  153. write(de.value, builder, indentation);
  154. write_comma = true;
  155. }
  156. write_new_line(builder, indentation);
  157. builder.append_c('}');
  158. }
  159. static void skip_whitespace(uint8[] json, ref int index)
  160. {
  161. while (index < json.length) {
  162. uint8 c = json[index];
  163. if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',')
  164. ++index;
  165. else
  166. break;
  167. }
  168. }
  169. static void consume(uint8[] json, ref int index, string consume) throws JsonSyntaxError
  170. {
  171. skip_whitespace(json, ref index);
  172. for (int i = 0; i < consume.length; ++i) {
  173. if (json[index] != consume[i])
  174. throw new JsonSyntaxError.BAD_TOKEN("Expected '%c' got '%c'".printf(consume[i], json[index]));
  175. ++index;
  176. }
  177. }
  178. static Value? parse(uint8[] json, ref int index) throws JsonSyntaxError
  179. {
  180. uint8 c = next(json, ref index);
  181. if (c == '{') {
  182. return parse_object(json, ref index);
  183. } else if (c == '[') {
  184. return parse_array(json, ref index);
  185. } else if (c == '"') {
  186. return parse_string(json, ref index);
  187. } else if (c == '-' || c >= '0' && c <= '9') {
  188. return parse_number(json, ref index);
  189. } else if (c == 't') {
  190. consume(json, ref index, "true");
  191. return true;
  192. } else if (c == 'f') {
  193. consume(json, ref index, "false");
  194. return false;
  195. } else if (c == 'n') {
  196. consume(json, ref index, "null");
  197. return null;
  198. } else {
  199. throw new JsonSyntaxError.BAD_VALUE("Bad value");
  200. }
  201. }
  202. static uint8 next(uint8[] json, ref int index)
  203. {
  204. skip_whitespace(json, ref index);
  205. return json[index];
  206. }
  207. static Hashtable parse_object(uint8[] json, ref int index) throws JsonSyntaxError
  208. {
  209. Hashtable ht = new Hashtable();
  210. consume(json, ref index, "{");
  211. skip_whitespace(json, ref index);
  212. while (next(json, ref index) != '}') {
  213. string key = parse_string(json, ref index);
  214. consume(json, ref index, ":");
  215. if (key.has_suffix("_binary"))
  216. ht[key] = (Value?)parse_binary(json, ref index);
  217. else
  218. ht[key] = parse(json, ref index);
  219. }
  220. consume(json, ref index, "}");
  221. return ht;
  222. }
  223. static Gee.ArrayList<Value?> parse_array(uint8[] json, ref int index) throws JsonSyntaxError
  224. {
  225. Gee.ArrayList<Value?> a = new Gee.ArrayList<Value?>();
  226. consume(json, ref index, "[");
  227. while (next(json, ref index) != ']') {
  228. Value? value = parse(json, ref index);
  229. a.add(value);
  230. }
  231. consume(json, ref index, "]");
  232. return a;
  233. }
  234. static uint8[] parse_binary(uint8[] json, ref int index) throws JsonSyntaxError
  235. {
  236. Gee.ArrayList<uint8> s = new Gee.ArrayList<uint8>();
  237. consume(json, ref index, "\"");
  238. while (true) {
  239. uint8 c = json[index];
  240. ++index;
  241. if (c == '"') {
  242. break;
  243. } else if (c != '\\') {
  244. s.add(c);
  245. } else {
  246. uint8 q = json[index];
  247. ++index;
  248. if (q == '"' || q == '\\' || q == '/')
  249. s.add(q);
  250. else if (q == 'b')
  251. s.add('\b');
  252. else if (q == 'f')
  253. s.add('\f');
  254. else if (q == 'n')
  255. s.add('\n');
  256. else if (q == 'r')
  257. s.add('\r');
  258. else if (q == 't')
  259. s.add('\t');
  260. else if (q == 'u')
  261. throw new JsonSyntaxError.BAD_STRING("Unsupported escape character 'u'");
  262. else
  263. throw new JsonSyntaxError.BAD_STRING("Bad string");
  264. }
  265. }
  266. s.add('\0');
  267. return s.to_array();
  268. }
  269. static string parse_string(uint8[] json, ref int index) throws JsonSyntaxError
  270. {
  271. return (string)parse_binary(json, ref index);
  272. }
  273. static double parse_number(uint8[] json, ref int index)
  274. {
  275. int end = index;
  276. while ("0123456789+-.eE".index_of_char((char)json[end]) != -1)
  277. ++end;
  278. uint8[] num = json[index : end];
  279. num += '\0';
  280. index = end;
  281. return double.parse((string)num);
  282. }
  283. }
  284. } /* namespace Crown */