About Social Code
summaryrefslogtreecommitdiff
path: root/json/main.rhm
blob: 7d71de2d0eee577cef2d0bfcc95d7e243b48ae38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#lang rhombus/static/and_meta

import:
  lib("racket/base.rkt").#{make-immutable-hashalw}
  "main.rkt" ! #{for-extension} as rkt_json:
    rename #{read-json*} as read
    rename #{write-json*} as write

def known_json = WeakMutableMap.by(===)()

// Using a `json` namespace adds a prefix to names
namespace json:
  export:
    JSON
    write
    to_string
    to_bytes
    read
    from_string
    from_bytes

  fun is_json(v):
    match v
    | _ :: (Int || (Flonum && Rational)): #true
    | _ :: String: #true
    | _ :: Boolean: #true
    | #'null: #true
    | vs :: List:
        cond
        | vs in known_json: #true
        | (for all (v in vs): is_json(v)):
            known_json[vs] := #true
            #true
        | ~else:
            #false
    | m :: Map:
        cond
        | m in known_json: #true
        | (for all ((k, v) in m):
             k is_a String && is_json(v)):
            known_json[m] := #true
            #true
        | ~else:
            #false
    | _: #false

  annot.macro 'JSON': 'satisfying(is_json)'

  fun do_write(who, v, out):
    rkt_json.write(who,
                   v,
                   out,
                   ~encode: #'control,
                   ~indent: #false,
                   ~null: #'null,
                   ~#{object-rep?}: (_ is_a Map),
                   ~#{object-rep->hash}: values,
                   ~#{list-rep?}: (_ is_a List),
                   ~#{list-rep->list}: fun (vs): PairList(&vs),
                   ~#{key-rep?}: (_ is_a String),
                   ~#{key-rep->string}: values,
                   ~#{string-rep?}: (_ is_a String),
                   ~#{string-rep->string}: values)

  fun write(v :: JSON,
            ~out: out :: Port.Output = Port.Output.current()):
    ~who: who
    do_write(who, v, out)

  fun to_string(v :: JSON) :~ String:
    ~who: who
    let s = Port.Output.open_string()
    do_write(who, v, s)
    s.get_string()

  fun to_bytes(v :: JSON) :~ Bytes:
    ~who: who
    let s = Port.Output.open_bytes()
    do_write(who, v, s)
    s.get_bytes()

  fun do_read(who, in, replace_malformed_surrogate):
    rkt_json.read(who,
                  in,
                  ~null: #'null,
                  ~#{make-object}: #{make-immutable-hashalw},
                  ~#{make-list}: PairList.to_list,
                  ~#{make-key}: String.snapshot,
                  ~#{make-string}: String.snapshot,
                  ~#{replace-malformed-surrogate?}: replace_malformed_surrogate)

  fun read(~in: in :: Port.Input = Port.Input.current(),
           ~replace_malformed_surrogate: replace_malformed_surrogate :: Any.to_boolean = #false):
    ~who: who
    do_read(who, in, replace_malformed_surrogate)

  fun check_eof(who, v, what, s):
    when v == Port.eof
    | error(~who: who,
            "no value in " ++ what)
    let v = do_read(who, s, #true)
    unless v == Port.eof
    | error(~who: who,
            "found additional value in " ++ what,
            error.val(~label: "additional value", v))

  fun from_string(s :: ReadableString,
                  ~replace_malformed_surrogate: replace_malformed_surrogate :: Any.to_boolean = #false):
    ~who: who
    let s = Port.Input.open_string(s)
    let v= do_read(who, s, replace_malformed_surrogate)
    check_eof(who, v, "string", s)
    v

  fun from_bytes(s :: Bytes,
                 ~replace_malformed_surrogate: replace_malformed_surrogate :: Any.to_boolean = #false):
    ~who: who
    let s = Port.Input.open_bytes(s)
    let v = do_read(who, s, replace_malformed_surrogate)
    check_eof(who, v, "byte string", s)
    v

export:
  all_from(.json)