1 ! Copyright (C) 2006 Chris Double, 2008 Peter Burns, 2009 Philipp Winkler
3 USING: accessors ascii assocs combinators formatting hashtables
4 io io.encodings.utf16.private io.encodings.utf8 io.files
5 io.streams.string kernel kernel.private linked-assocs make math
6 math.order math.parser mirrors namespaces sbufs sequences
7 sequences.private strings summary tr words vocabs.loader ;
15 ERROR: json-fp-special-error value ;
17 M: json-fp-special-error summary
18 drop "JSON serialization: illegal float:" ;
20 : if-json-null ( x if-null else -- )
25 : json-null>f ( obj/json-null -- obj/f )
26 dup json-null = [ drop f ] when ; inline
28 : when-json-null ( x if-null -- ) [ ] if-json-null ; inline
30 : unless-json-null ( x else -- ) [ ] swap if-json-null ; inline
34 ERROR: not-a-json-number string ;
38 : json-number ( char stream -- num char )
39 [ 1string ] [ "\s\t\r\n,:}]" swap stream-read-until ] bi*
42 { "Infinity" [ 1/0. ] }
43 { "-Infinity" [ -1/0. ] }
45 [ [ string>number ] [ not-a-json-number ] ?unless ]
49 : json-expect ( token stream -- )
50 [ dup length ] [ stream-read ] bi* = [ json-error ] unless ; inline
52 DEFER: (read-json-string)
54 : decode-utf16-surrogate-pair ( hex1 hex2 -- char )
55 [ 0x3ff bitand ] bi@ [ 10 shift ] dip bitor 0x10000 + ;
57 : stream-read-4hex ( stream -- hex ) 4 swap stream-read hex> ;
59 : first-surrogate? ( hex -- ? ) 0xd800 0xdbff between? ;
61 : read-second-surrogate ( stream -- hex )
62 "\\u" over json-expect stream-read-4hex ;
64 : read-json-escape-unicode ( stream -- char )
65 [ stream-read-4hex ] keep over first-surrogate? [
66 read-second-surrogate decode-utf16-surrogate-pair
69 : (read-json-escape) ( stream accum -- accum )
72 { CHAR: \" [ CHAR: \" ] }
73 { CHAR: \\ [ CHAR: \\ ] }
74 { CHAR: / [ CHAR: / ] }
75 { CHAR: b [ CHAR: \b ] }
76 { CHAR: f [ CHAR: \f ] }
77 { CHAR: n [ CHAR: \n ] }
78 { CHAR: r [ CHAR: \r ] }
79 { CHAR: t [ CHAR: \t ] }
80 { CHAR: u [ over read-json-escape-unicode ] }
82 } case [ suffix! (read-json-string) ] [ json-error ] if* ;
84 : (read-json-string) ( stream accum -- accum )
86 "\\\"" pick stream-read-until [ append! ] dip
87 CHAR: \" = [ nip ] [ (read-json-escape) ] if ;
89 : read-json-string ( stream -- str )
90 "\\\"" over stream-read-until CHAR: \" =
91 [ nip ] [ >sbuf (read-json-escape) "" like ] if ;
93 : second-last-unsafe ( seq -- second-last )
94 [ length 2 - ] [ nth-unsafe ] bi ; inline
96 : pop-unsafe ( seq -- elt )
97 index-of-last [ nth-unsafe ] [ shorten ] 2bi ; inline
99 : check-length ( seq n -- seq )
100 [ dup length ] [ >= ] bi* [ json-error ] unless ; inline
102 : v-over-push ( accum -- accum )
103 2 check-length dup [ pop-unsafe ] [ last-unsafe ] bi push ;
105 : v-pick-push ( accum -- accum )
106 3 check-length dup [ pop-unsafe ] [ second-last-unsafe ] bi push ;
108 : v-close ( accum -- accum )
109 dup last V{ } = not [ v-over-push ] when ;
111 : json-open-array ( accum -- accum )
114 : json-open-hash ( accum -- accum )
115 V{ } clone suffix! V{ } clone suffix! ;
117 : json-close-array ( accum -- accum )
118 v-close dup pop { } like suffix! ;
120 : json-close-hash ( accum -- accum )
121 v-close dup dup [ pop ] bi@ swap LH{ } zip-as suffix! ;
123 : scan ( stream accum char -- stream accum )
124 ! 2dup 1string swap . . ! Great for debug...
126 { CHAR: \" [ over read-json-string suffix! ] }
127 { CHAR: [ [ 1 json-depth +@ json-open-array ] }
128 { CHAR: , [ v-over-push ] }
129 { CHAR: ] [ -1 json-depth +@ json-close-array ] }
130 { CHAR: { [ json-open-hash ] }
131 { CHAR: : [ v-pick-push ] }
132 { CHAR: } [ json-close-hash ] }
137 { CHAR: t [ "rue" pick json-expect t suffix! ] }
138 { CHAR: f [ "alse" pick json-expect f suffix! ] }
139 { CHAR: n [ "ull" pick json-expect json-null suffix! ] }
140 [ pick json-number [ suffix! ] dip [ scan ] when* ]
143 : get-json ( objects -- obj )
144 dup length 1 = [ first ] [ json-error ] if ;
148 : stream-read-json ( stream -- objects )
150 V{ } clone over '[ _ stream-read1 ] [ scan ] while* nip
151 json-depth get zero? [ json-error ] unless
154 : read-json ( -- objects )
155 input-stream get stream-read-json ;
157 GENERIC: json> ( string -- object )
160 [ read-json get-json ] with-string-reader ;
162 SYMBOL: json-allow-fp-special?
163 f json-allow-fp-special? set-global
165 SYMBOL: json-friendly-keys?
166 t json-friendly-keys? set-global
168 SYMBOL: json-coerce-keys?
169 t json-coerce-keys? set-global
171 SYMBOL: json-escape-slashes?
172 f json-escape-slashes? set-global
174 SYMBOL: json-escape-unicode?
175 f json-escape-unicode? set-global
177 ! Writes the object out to a stream in JSON format
178 GENERIC#: stream-write-json 1 ( obj stream -- )
180 : write-json ( obj -- )
181 output-stream get stream-write-json ;
183 : >json ( obj -- string )
184 ! Returns a string representing the factor object in JSON format
185 [ write-json ] with-string-writer ;
187 M: f stream-write-json
188 [ drop "false" ] [ stream-write ] bi* ;
190 M: t stream-write-json
191 [ drop "true" ] [ stream-write ] bi* ;
193 M: json-null stream-write-json
194 [ drop "null" ] [ stream-write ] bi* ;
198 : write-json-generic-escape-surrogate-pair ( stream char -- stream )
199 0x10000 - [ encode-first ] [ encode-second ] bi
200 "\\u%02x%02x\\u%02x%02x" sprintf over stream-write ;
202 : write-json-generic-escape-bmp ( stream char -- stream )
203 "\\u%04x" sprintf over stream-write ;
205 : write-json-generic-escape ( stream char -- stream )
207 write-json-generic-escape-surrogate-pair
209 write-json-generic-escape-bmp
214 M: string stream-write-json
215 CHAR: \" over stream-write1 swap [
217 { CHAR: \" [ "\\\"" over stream-write ] }
218 { CHAR: \\ [ "\\\\" over stream-write ] }
220 json-escape-slashes? get
221 [ "\\/" over stream-write ]
222 [ CHAR: / over stream-write1 ] if
224 { CHAR: \b [ "\\b" over stream-write ] }
225 { CHAR: \f [ "\\f" over stream-write ] }
226 { CHAR: \n [ "\\n" over stream-write ] }
227 { CHAR: \r [ "\\r" over stream-write ] }
228 { CHAR: \t [ "\\t" over stream-write ] }
229 { 0x2028 [ "\\u2028" over stream-write ] }
230 { 0x2029 [ "\\u2029" over stream-write ] }
233 { [ dup printable? ] [ f ] }
234 { [ dup control? ] [ t ] }
235 [ json-escape-unicode? get ]
237 write-json-generic-escape
243 ] each CHAR: \" swap stream-write1 ;
245 M: integer stream-write-json
246 [ number>string ] [ stream-write ] bi* ;
248 : float>json ( float -- string )
250 json-allow-fp-special? get [ json-fp-special-error ] unless
252 { [ dup fp-nan? ] [ drop "NaN" ] }
253 { [ dup 1/0. = ] [ drop "Infinity" ] }
254 { [ dup -1/0. = ] [ drop "-Infinity" ] }
260 M: float stream-write-json
261 [ float>json ] [ stream-write ] bi* ;
263 M: real stream-write-json
264 [ >float number>string ] [ stream-write ] bi* ;
266 M: sequence stream-write-json
267 CHAR: [ over stream-write1 swap
268 over '[ CHAR: , _ stream-write1 ]
269 pick '[ _ stream-write-json ] interleave
270 CHAR: ] swap stream-write1 ;
274 TR: json-friendly "-" "_" ;
276 GENERIC: json-coerce ( obj -- str )
277 M: f json-coerce drop "false" ;
278 M: t json-coerce drop "true" ;
279 M: json-null json-coerce drop "null" ;
280 M: string json-coerce ;
281 M: integer json-coerce number>string ;
282 M: float json-coerce float>json ;
283 M: real json-coerce >float number>string ;
285 :: write-json-assoc ( obj stream -- )
286 CHAR: { stream stream-write1 obj >alist
287 [ CHAR: , stream stream-write1 ]
288 json-friendly-keys? get
289 json-coerce-keys? get '[
292 [ _ [ json-friendly ] when ]
293 [ _ [ json-coerce ] when ] if
294 stream stream-write-json
296 CHAR: : stream stream-write1
297 stream stream-write-json
300 CHAR: } stream stream-write1 ;
304 M: tuple stream-write-json
305 [ <mirror> ] dip write-json-assoc ;
307 M: assoc stream-write-json write-json-assoc ;
309 M: word stream-write-json
310 [ name>> ] dip stream-write-json ;
312 : ?>json ( obj -- json ) dup string? [ >json ] unless ;
313 : ?json> ( obj -- json/f ) f like [ json> ] ?call ;
315 : stream-read-jsonlines ( stream -- objects )
316 [ [ json> , ] each-stream-line ] { } make ;
318 : read-jsonlines ( -- objects )
319 input-stream get stream-read-jsonlines ;
321 GENERIC: jsonlines> ( string -- objects )
324 [ read-jsonlines ] with-string-reader ;
326 : stream-write-jsonlines ( objects stream -- )
327 [ stream-nl ] [ stream-write-json ] bi-curry interleave ;
329 : write-jsonlines ( objects -- )
330 output-stream get stream-write-jsonlines ;
332 : >jsonlines ( objects -- string )
333 [ write-jsonlines ] with-string-writer ;
335 : path>json ( path -- json )
336 utf8 [ read-json get-json ] with-file-reader ;
338 : path>jsons ( path -- jsons )
339 utf8 [ read-json ] with-file-reader ;
341 : json>path ( json path -- )
342 utf8 [ write-json ] with-file-writer ;
344 : jsons>path ( jsons path -- )
345 utf8 [ write-jsonlines ] with-file-writer ;
347 : rewrite-json-string ( string quot: ( json -- json' ) -- string )
348 [ json> ] dip call >json ; inline
350 : rewrite-jsons-string ( string quot: ( jsons -- jsons' ) -- string )
351 [ jsonlines> ] dip call >jsonlines ; inline
353 : rewrite-json-path ( path quot: ( json -- json' ) -- )
354 [ [ path>json ] dip call ] keepd json>path ; inline
356 : rewrite-jsons-path ( path quot: ( jsons -- jsons' ) -- )
357 [ [ path>jsons ] dip call ] keepd jsons>path ; inline
359 { "json" "ui.tools" } "json.ui" require-when