json-util.sml.orig 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. (* json-util.sml
  2. *
  3. * COPYRIGHT (c) 2017 The Fellowship of SML/NJ (http://www.smlnj.org)
  4. * All rights reserved.
  5. *
  6. * Utility functions for processing the JSON in-memory representation.
  7. *)
  8. structure JSONUtil : sig
  9. (* exceptions for conversion functions *)
  10. exception NotBool of JSON.value
  11. exception NotInt of JSON.value
  12. exception NotNumber of JSON.value
  13. exception NotString of JSON.value
  14. (* exception that is raised when trying to process a non-object value as an object *)
  15. exception NotObject of JSON.value
  16. (* exception that is raised when the given field is not found in an object *)
  17. exception FieldNotFound of JSON.value * string
  18. (* exception that is raised when trying to process a non-array value as an array *)
  19. exception NotArray of JSON.value
  20. (* exception that is raise when access to an array value is out of bounds *)
  21. exception ArrayBounds of JSON.value * int
  22. (* map the above exceptions to a message string; we use General.exnMessage for other
  23. * exceptions.
  24. *)
  25. val exnMessage : exn -> string
  26. (* conversion functions for atomic values. These raise the corresponding
  27. * "NotXXX" exceptions when their argument has the wrong shape. Also note
  28. * that asNumber will accept both integers and floats and asInt may raise
  29. * Overflow if the number is too large.
  30. *)
  31. val asBool : JSON.value -> bool
  32. val asInt : JSON.value -> Int.int
  33. val asIntInf : JSON.value -> IntInf.int
  34. val asNumber : JSON.value -> Real.real
  35. val asString : JSON.value -> string
  36. (* find a field in an object; this function raises the NotObject exception when
  37. * the supplied value is not an object.
  38. *)
  39. val findField : JSON.value -> string -> JSON.value option
  40. (* lookup a field in an object; this function raises the NotObject exception when
  41. * the supplied value is not an object and raises FieldNotFound if the value is
  42. * an object, but does not have the specified field.
  43. *)
  44. val lookupField : JSON.value -> string -> JSON.value
  45. (* convert a JSON array to an SML vector *)
  46. val asArray : JSON.value -> JSON.value vector
  47. (* map a conversion function over a JSON array to produce a list; this function
  48. * raises the NotArray exception if the second argument is not an array.
  49. *)
  50. val arrayMap : (JSON.value -> 'a) -> JSON.value -> 'a list
  51. (* path specification for indexing into JSON values *)
  52. datatype edge
  53. = SUB of int (* index into array component *)
  54. | SEL of string (* select field of object *)
  55. type path = edge list
  56. (* `get (jv, path)` returns the component of `jv` named by `path`. It raises
  57. * the NotObject, NotArray, and FieldNotFound exceptions if there is an inconsistency
  58. * between the path and the structure of `jv`.
  59. *)
  60. val get : JSON.value * path -> JSON.value
  61. (* `replace (jv, path, v)` replaces the component of `jv` named by `path`
  62. * with the value `v`.
  63. *)
  64. val replace : JSON.value * path * JSON.value -> JSON.value
  65. (* `insert (jv, path, lab, v)` inserts `lab : v` into the object named by `path`
  66. * in `jv`
  67. *)
  68. val insert : JSON.value * path * string * JSON.value -> JSON.value
  69. (* `append (jv, path, vs)` appends the list of values `vs` onto the array named by `path`
  70. * in `jv`
  71. *)
  72. val append : JSON.value * path * JSON.value list -> JSON.value
  73. end = struct
  74. structure J = JSON
  75. exception NotBool of J.value
  76. exception NotInt of J.value
  77. exception NotNumber of J.value
  78. exception NotString of J.value
  79. exception NotObject of J.value
  80. exception FieldNotFound of J.value * string
  81. exception NotArray of J.value
  82. exception ArrayBounds of J.value * int
  83. (* conversion functions for atomic values *)
  84. fun asBool (J.BOOL b) = b
  85. | asBool v = raise NotBool v
  86. fun asInt (J.INT n) = Int.fromLarge n
  87. | asInt v = raise NotInt v
  88. fun asIntInf (J.INT n) = n
  89. | asIntInf v = raise NotInt v
  90. fun asNumber (J.INT n) = Real.fromLargeInt n
  91. | asNumber (J.FLOAT f) = f
  92. | asNumber v = raise NotNumber v
  93. fun asString (J.STRING s) = s
  94. | asString v = raise NotString v
  95. fun findField (J.OBJECT fields) = let
  96. fun find lab = (case List.find (fn (l, v) => (l = lab)) fields
  97. of NONE => NONE
  98. | SOME(_, v) => SOME v
  99. (* end case *))
  100. in
  101. find
  102. end
  103. | findField v = raise NotObject v
  104. fun lookupField (v as J.OBJECT fields) = let
  105. fun find lab = (case List.find (fn (l, v) => (l = lab)) fields
  106. of NONE => raise FieldNotFound(v, concat["no definition for field \"", lab, "\""])
  107. | SOME(_, v) => v
  108. (* end case *))
  109. in
  110. find
  111. end
  112. | lookupField v = raise NotObject v
  113. fun asArray (J.ARRAY vs) = Vector.fromList vs
  114. | asArray v = raise NotArray v
  115. fun arrayMap f (J.ARRAY vs) = List.map f vs
  116. | arrayMap f v = raise NotArray v
  117. (* map the above exceptions to a message string; we use General.exnMessage for other
  118. * exceptions.
  119. *)
  120. fun exnMessage exn = let
  121. fun v2s (J.ARRAY _) = "array"
  122. | v2s (J.BOOL false) = "'false'"
  123. | v2s (J.BOOL true) = "'true'"
  124. | v2s (J.FLOAT _) = "number"
  125. | v2s (J.INT _) = "number"
  126. | v2s J.NULL = "'null'"
  127. | v2s (J.OBJECT _) = "object"
  128. | v2s (J.STRING _) = "string"
  129. in
  130. case exn
  131. of NotBool v => String.concat[
  132. "expected boolean, but found ", v2s v
  133. ]
  134. | NotInt(J.FLOAT _) => "expected integer, but found floating-point number"
  135. | NotInt v => String.concat[
  136. "expected integer, but found ", v2s v
  137. ]
  138. | NotNumber v => String.concat[
  139. "expected number, but found ", v2s v
  140. ]
  141. | NotString v => String.concat[
  142. "expected string, but found ", v2s v
  143. ]
  144. | NotObject v => String.concat[
  145. "expected object, but found ", v2s v
  146. ]
  147. | FieldNotFound(v, fld) => String.concat[
  148. "no definition for field \"", fld, "\" in object"
  149. ]
  150. | NotArray v => String.concat[
  151. "expected array, but found ", v2s v
  152. ]
  153. | _ => General.exnMessage exn
  154. (* end case *)
  155. end
  156. (* path specification for indexing into JSON values *)
  157. datatype edge
  158. = SEL of string (* select field of object *)
  159. | SUB of int (* index into array component *)
  160. type path = edge list
  161. fun get (v, []) = v
  162. | get (v as J.OBJECT fields, SEL lab :: rest) =
  163. (case List.find (fn (l, v) => (l = lab)) fields
  164. of NONE => raise raise FieldNotFound(v, concat["no definition for field \"", lab, "\""])
  165. | SOME(_, v) => get (v, rest)
  166. (* end case *))
  167. | get (v, SEL _ :: _) = raise NotObject v
  168. | get (J.ARRAY vs, SUB i :: rest) = get (List.nth(vs, i), rest)
  169. | get (v, SUB _ :: _) = raise (NotArray v)
  170. (* top-down zipper to support functional editing *)
  171. datatype zipper
  172. = ZNIL
  173. | ZOBJ of {
  174. prefix : (string * J.value) list,
  175. label : string,
  176. child : zipper,
  177. suffix : (string * J.value) list
  178. }
  179. | ZARR of {
  180. prefix : J.value list,
  181. child : zipper,
  182. suffix : J.value list
  183. }
  184. (* follow a path into a JSON value while constructing a zipper *)
  185. fun unzip (v, []) = (ZNIL, v)
  186. | unzip (v as J.OBJECT fields, SEL lab :: rest) = let
  187. fun find (_, []) = raise FieldNotFound(v, concat["no definition for field \"", lab, "\""])
  188. | find (pre, (l, v)::flds) = if (l = lab)
  189. then let
  190. val (zipper, v) = unzip (v, rest)
  191. in
  192. (ZOBJ{prefix=pre, label=lab, suffix=flds, child=zipper}, v)
  193. end
  194. else find ((l, v)::pre, flds)
  195. in
  196. find ([], fields)
  197. end
  198. | unzip (v, SEL _ :: _) = raise NotObject v
  199. | unzip (v as J.ARRAY vs, SUB i :: rest) = let
  200. fun sub (_, [], _) = raise ArrayBounds(v, i)
  201. | sub (prefix, v::vs, 0) = let
  202. val (zipper, v) = unzip (v, rest)
  203. in
  204. (ZARR{prefix = prefix, child = zipper, suffix = vs}, v)
  205. end
  206. | sub (prefix, v::vs, i) = sub(v::prefix, vs, i-1)
  207. in
  208. sub ([], vs, i)
  209. end
  210. | unzip (v, SUB _ :: _) = raise NotArray v
  211. (* zip up a zipper *)
  212. fun zip (zipper, v) = let
  213. fun zip' ZNIL = v
  214. | zip' (ZOBJ{prefix, label, suffix, child}) =
  215. J.OBJECT(List.revAppend(prefix, (label, zip' child)::suffix))
  216. | zip' (ZARR{prefix, child, suffix}) =
  217. J.ARRAY(List.revAppend(prefix, zip' child :: suffix))
  218. in
  219. zip' zipper
  220. end
  221. fun replace (jv, path, v) = zip (#1 (unzip (jv, path)), v)
  222. fun insert (jv, path, label, v) = (case unzip (jv, path)
  223. of (zipper, J.OBJECT fields) => zip (zipper, J.OBJECT((label, v)::fields))
  224. | (_, v) => raise NotObject v
  225. (* end case *))
  226. fun append (jv, path, vs) = (case unzip (jv, path)
  227. of (zipper, J.ARRAY jvs) => zip (zipper, J.ARRAY(jvs @ vs))
  228. | (_, v) => raise NotArray v
  229. (* end case *))
  230. end