Home My Page Projects Code Snippets Project Openings SML/NJ
Summary Activity Forums Tracker Lists Tasks Docs Surveys News SCM Files

SCM Repository

[smlnj] View of /smlnj-lib/trunk/JSON/json-util.sml
ViewVC logotype

View of /smlnj-lib/trunk/JSON/json-util.sml

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4333 - (download) (annotate)
Sat Apr 29 15:39:27 2017 UTC (2 years, 1 month ago) by jhr
File size: 8967 byte(s)
remove extra whitespace
(* json-util.sml
 *
 * COPYRIGHT (c) 2017 The Fellowship of SML/NJ (http://www.smlnj.org)
 * All rights reserved.
 *
 * Utility functions for processing the JSON in-memory representation.
 *)

structure JSONUtil : sig

  (* exceptions for conversion functions *)
    exception NotBool of JSON.value
    exception NotInt of JSON.value
    exception NotNumber of JSON.value
    exception NotString of JSON.value

  (* exception that is raised when trying to process a non-object value as an object *)
    exception NotObject of JSON.value

  (* exception that is raised when the given field is not found in an object *)
    exception FieldNotFound of JSON.value * string

  (* exception that is raised when trying to process a non-array value as an array *)
    exception NotArray of JSON.value

  (* exception that is raise when access to an array value is out of bounds *)
    exception ArrayBounds of JSON.value * int

  (* map the above exceptions to a message string; we use General.exnMessage for other
   * exceptions.
   *)
    val exnMessage : exn -> string

  (* conversion functions for atomic values.  These raise the corresponding
   * "NotXXX" exceptions when their argument has the wrong shape.  Also note
   * that asNumber will accept both integers and floats and asInt may raise
   * Overflow if the number is too large.
   *)
    val asBool : JSON.value -> bool
    val asInt : JSON.value -> Int.int
    val asIntInf : JSON.value -> IntInf.int
    val asNumber : JSON.value -> Real.real
    val asString : JSON.value -> string

  (* find a field in an object; this function raises the NotObject exception when
   * the supplied value is not an object.
   *)
    val findField : JSON.value -> string -> JSON.value option

  (* lookup a field in an object; this function raises the NotObject exception when
   * the supplied value is not an object and raises FieldNotFound if the value is
   * an object, but does not have the specified field.
   *)
    val lookupField : JSON.value -> string -> JSON.value

  (* convert a JSON array to an SML vector *)
    val asArray : JSON.value -> JSON.value vector

  (* map a conversion function over a JSON array to produce a list; this function
   * raises the NotArray exception if the second argument is not an array.
   *)
    val arrayMap : (JSON.value -> 'a) -> JSON.value -> 'a list

  (* path specification for indexing into JSON values *)
    datatype edge
      = SUB of int      (* index into array component *)
      | SEL of string   (* select field of object *)

    type path = edge list

  (* `get (jv, path)` returns the component of `jv` named by `path`.  It raises
   * the NotObject, NotArray, and FieldNotFound exceptions if there is an inconsistency
   * between the path and the structure of `jv`.
   *)
    val get : JSON.value * path -> JSON.value

  (* `replace (jv, path, v)` replaces the component of `jv` named by `path`
   * with the value `v`.
   *)
    val replace : JSON.value * path * JSON.value -> JSON.value

  (* `insert (jv, path, lab, v)` inserts `lab : v` into the object named by `path`
   * in `jv`
   *)
    val insert : JSON.value * path * string * JSON.value -> JSON.value

  (* `append (jv, path, vs)` appends the list of values `vs` onto the array named by `path`
   * in `jv`
   *)
    val append : JSON.value * path * JSON.value list -> JSON.value

  end = struct

    structure J = JSON

    exception NotBool of J.value
    exception NotInt of J.value
    exception NotNumber of J.value
    exception NotString of J.value

    exception NotObject of J.value
    exception FieldNotFound of J.value * string

    exception NotArray of J.value
    exception ArrayBounds of J.value * int

  (* conversion functions for atomic values *)
    fun asBool (J.BOOL b) = b
      | asBool v = raise NotBool v

    fun asInt (J.INT n) = Int.fromLarge n
      | asInt v = raise NotInt v

    fun asIntInf (J.INT n) = n
      | asIntInf v = raise NotInt v

    fun asNumber (J.INT n) = Real.fromLargeInt n
      | asNumber (J.FLOAT f) = f
      | asNumber v = raise NotNumber v

    fun asString (J.STRING s) = s
      | asString v = raise NotString v

    fun findField (J.OBJECT fields) = let
	  fun find lab = (case List.find (fn (l, v) => (l = lab)) fields
		 of NONE => NONE
		  | SOME(_, v) => SOME v
		(* end case *))
	  in
	    find
	  end
      | findField v = raise NotObject v

    fun lookupField (v as J.OBJECT fields) = let
	  fun find lab = (case List.find (fn (l, v) => (l = lab)) fields
		 of NONE => raise FieldNotFound(v, concat["no definition for field \"", lab, "\""])
		  | SOME(_, v) => v
		(* end case *))
	  in
	    find
	  end
      | lookupField v = raise NotObject v

    fun asArray (J.ARRAY vs) = Vector.fromList vs
      | asArray v = raise NotArray v

    fun arrayMap f (J.ARRAY vs) = List.map f vs
      | arrayMap f v = raise NotArray v

  (* map the above exceptions to a message string; we use General.exnMessage for other
   * exceptions.
   *)
    fun exnMessage exn = let
	  fun v2s (J.ARRAY _) = "array"
	    | v2s (J.BOOL false) = "'false'"
	    | v2s (J.BOOL true) = "'true'"
	    | v2s (J.FLOAT _) = "number"
	    | v2s (J.INT _) = "number"
	    | v2s J.NULL = "'null'"
	    | v2s (J.OBJECT _) = "object"
	    | v2s (J.STRING _) = "string"
	  in
	    case exn
	     of NotBool v => String.concat[
		    "expected boolean, but found ", v2s v
		  ]
	      | NotInt(J.FLOAT _) => "expected integer, but found floating-point number"
	      | NotInt v => String.concat[
		    "expected integer, but found ", v2s v
		  ]
	      | NotNumber v => String.concat[
		    "expected number, but found ", v2s v
		  ]
	      | NotString v => String.concat[
		    "expected string, but found ", v2s v
		  ]
	      | NotObject v => String.concat[
		    "expected object, but found ", v2s v
		  ]
	      | FieldNotFound(v, fld) => String.concat[
		    "no definition for field \"", fld, "\" in object"
		  ]
	      | NotArray v => String.concat[
		    "expected array, but found ", v2s v
		  ]
	      | _ => General.exnMessage exn
	    (* end case *)
	  end

  (* path specification for indexing into JSON values *)
    datatype edge
      = SEL of string   (* select field of object *)
      | SUB of int      (* index into array component *)

    type path = edge list

    fun get (v, []) = v
      | get (v as J.OBJECT fields, SEL lab :: rest) =
	  (case List.find (fn (l, v) => (l = lab)) fields
	   of NONE => raise raise FieldNotFound(v, concat["no definition for field \"", lab, "\""])
	    | SOME(_, v) => get (v, rest)
	  (* end case *))
      | get (v, SEL _ :: _) = raise NotObject v
      | get (J.ARRAY vs, SUB i :: rest) = get (List.nth(vs, i), rest)
      | get (v, SUB _ :: _) = raise (NotArray v)

  (* top-down zipper to support functional editing *)
    datatype zipper
      = ZNIL
      | ZOBJ of {
            prefix : (string * J.value) list,
            label : string,
            child : zipper,
            suffix : (string * J.value) list
          }
      | ZARR of {
            prefix : J.value list,
            child : zipper,
            suffix : J.value list
          }

  (* follow a path into a JSON value while constructing a zipper *)
    fun unzip (v, []) = (ZNIL, v)
      | unzip (v as J.OBJECT fields, SEL lab :: rest) = let
          fun find (_, []) = raise FieldNotFound(v, concat["no definition for field \"", lab, "\""])
            | find (pre, (l, v)::flds) = if (l = lab)
                then let
		  val (zipper, v) = unzip (v, rest)
		  in
		    (ZOBJ{prefix=pre, label=lab, suffix=flds, child=zipper}, v)
                  end
                else find ((l, v)::pre, flds)
          in
            find ([], fields)
          end
      | unzip (v, SEL _ :: _) = raise NotObject v
      | unzip (v as J.ARRAY vs, SUB i :: rest) = let
          fun sub (_, [], _) = raise ArrayBounds(v, i)
            | sub (prefix, v::vs, 0) = let
		val (zipper, v) = unzip (v, rest)
		in
		  (ZARR{prefix = prefix, child = zipper, suffix = vs}, v)
		end
            | sub (prefix, v::vs, i) = sub(v::prefix, vs, i-1)
	  in
	    sub ([], vs, i)
	  end
      | unzip (v, SUB _ :: _) = raise NotArray v

  (* zip up a zipper *)
    fun zip (zipper, v) = let
	  fun zip' ZNIL = v
            | zip' (ZOBJ{prefix, label, suffix, child}) =
                J.OBJECT(List.revAppend(prefix, (label, zip' child)::suffix))
            | zip' (ZARR{prefix, child, suffix}) =
                J.ARRAY(List.revAppend(prefix, zip' child :: suffix))
          in
	    zip' zipper
	  end

    fun replace (jv, path, v) = zip (#1 (unzip (jv, path)), v)

    fun insert (jv, path, label, v) = (case unzip (jv, path)
	   of (zipper, J.OBJECT fields) => zip (zipper, J.OBJECT((label, v)::fields))
	    | (_, v) => raise NotObject v
	  (* end case *))

    fun append (jv, path, vs) = (case unzip (jv, path)
	   of (zipper, J.ARRAY jvs) => zip (zipper, J.ARRAY(jvs @ vs))
	    | (_, v) => raise NotArray v
	  (* end case *))

  end

root@smlnj-gforge.cs.uchicago.edu
ViewVC Help
Powered by ViewVC 1.0.0