OILS / spec / ysh-json.test.sh View on Github | oilshell.org

1000 lines, 262 significant
1## oils_failures_allowed: 2
2## tags: dev-minimal
3
4#### usage errors
5
6json read zz
7echo status=$?
8
9json write
10
11## status: 3
12## STDOUT:
13status=2
14## END
15
16#### json write STRING
17shopt --set parse_proc
18
19json write ('foo')
20var s = 'foo'
21json write (s)
22## STDOUT:
23"foo"
24"foo"
25## END
26
27#### json write ARRAY
28json write (:|foo.cc foo.h|)
29json write --indent 0 (['foo.cc', 'foo.h'])
30## STDOUT:
31[
32 "foo.cc",
33 "foo.h"
34]
35[
36"foo.cc",
37"foo.h"
38]
39## END
40
41#### json write Dict
42json write ({k: 'v', k2: [4, 5]})
43
44json write ([{k: 'v', k2: 'v2'}, {}])
45
46## STDOUT:
47{
48 "k": "v",
49 "k2": [
50 4,
51 5
52 ]
53}
54[
55 {
56 "k": "v",
57 "k2": "v2"
58 },
59 {}
60]
61## END
62
63#### json write compact format
64shopt --set parse_proc
65
66# TODO: ORDER of keys should be PRESERVED
67var mydict = {name: "bob", age: 30}
68
69json write --pretty=0 (mydict)
70# ignored
71json write --pretty=F --indent 4 (mydict)
72## STDOUT:
73{"name":"bob","age":30}
74{"name":"bob","age":30}
75## END
76
77#### json write in command sub
78shopt -s oil:all # for echo
79var mydict = {name: "bob", age: 30}
80json write (mydict)
81var x = $(json write (mydict))
82echo $x
83## STDOUT:
84{
85 "name": "bob",
86 "age": 30
87}
88{
89 "name": "bob",
90 "age": 30
91}
92## END
93
94#### json read passed invalid args
95
96# EOF
97json read
98echo status=$?
99
100json read 'z z'
101echo status=$?
102
103json read a b c
104echo status=$?
105
106## STDOUT:
107status=1
108status=2
109status=2
110## END
111
112#### json read uses $_reply var
113
114# space before true
115echo ' true' | json read
116json write (_reply)
117
118## STDOUT:
119true
120## END
121
122#### json read then json write
123
124# BUG with space before true
125echo '{"name": "bob", "age": 42, "ok": true}' | json read
126json write (_reply)
127
128echo '{"name": "bob", "age": 42, "ok":true}' | json read
129json write (_reply)
130
131echo '{"name": {}, "age": {}}' | json read
132json write (_reply)
133
134## STDOUT:
135{
136 "name": "bob",
137 "age": 42,
138 "ok": true
139}
140{
141 "name": "bob",
142 "age": 42,
143 "ok": true
144}
145{
146 "name": {},
147 "age": {}
148}
149## END
150
151#### json read with redirect
152echo '{"age": 42}' > $TMP/foo.txt
153json read (&x) < $TMP/foo.txt
154pp cell :x
155## STDOUT:
156x = (Cell exported:F readonly:F nameref:F val:(value.Dict d:[Dict age (value.Int i:42)]))
157## END
158
159#### json read at end of pipeline (relies on lastpipe)
160echo '{"age": 43}' | json read (&y)
161pp cell y
162## STDOUT:
163y = (Cell exported:F readonly:F nameref:F val:(value.Dict d:[Dict age (value.Int i:43)]))
164## END
165
166#### invalid JSON
167echo '{' | json read (&y)
168echo pipeline status = $?
169pp line (y)
170## status: 1
171## STDOUT:
172pipeline status = 1
173## END
174
175#### Extra data after valid JSON
176
177# Trailing space is OK
178echo '42 ' | json read
179echo num space $?
180
181echo '{} ' | json read
182echo obj space $?
183
184echo '42 # comment' | json8 read
185echo num comment $?
186
187echo '{} # comment ' | json8 read
188echo obj comment $?
189
190echo '42]' | json read
191echo num bracket $?
192
193echo '{}]' | json read
194echo obj bracket $?
195
196## STDOUT:
197num space 0
198obj space 0
199num comment 0
200obj comment 0
201num bracket 1
202obj bracket 1
203## END
204
205#### json write expression
206json write --pretty=0 ([1,2,3])
207echo status=$?
208
209json write (5, 6) # to many args
210echo status=$?
211
212## status: 3
213## STDOUT:
214[1,2,3]
215status=0
216## END
217
218#### json write evaluation error
219
220#var block = ^(echo hi)
221#json write (block)
222#echo status=$?
223
224# undefined var
225json write (a)
226echo 'should have failed'
227
228## status: 1
229## STDOUT:
230## END
231
232#### json write of List in cycle
233
234var L = [1, 2, 3]
235setvar L[0] = L
236
237shopt -s ysh:upgrade
238fopen >tmp.txt {
239 pp line (L)
240}
241fgrep -n -o '[ -->' tmp.txt
242
243json write (L)
244echo 'should have failed'
245
246## status: 1
247## STDOUT:
2481:[ -->
249## END
250
251#### json write of Dict in cycle
252
253var d = {}
254setvar d.k = d
255
256shopt -s ysh:upgrade
257fopen >tmp.txt {
258 pp line (d)
259}
260fgrep -n -o '{ -->' tmp.txt
261
262json write (d)
263echo 'should have failed'
264
265## status: 1
266## STDOUT:
2671:{ -->
268## END
269
270#### json write of List/Dict referenced twice (bug fix)
271
272var mylist = [1,2,3]
273var mydict = {foo: "bar"}
274
275var top = {k: mylist, k2: mylist, k3: mydict, k4: mydict}
276
277# BUG!
278json write --pretty=F (top)
279
280## STDOUT:
281{"k":[1,2,3],"k2":[1,2,3],"k3":{"foo":"bar"},"k4":{"foo":"bar"}}
282## END
283
284#### json read doesn't accept u'' or b'' strings
285
286json read <<EOF
287{"key": u'val'}
288EOF
289echo status=$?
290
291#pp line (_reply)
292
293json read <<EOF
294{"key": b'val'}
295EOF
296echo status=$?
297
298## STDOUT:
299status=1
300status=1
301## END
302
303#### json read doesn't accept comments, but json8 does
304
305json8 read <<EOF
306{ # comment
307 "key": # zz
308 b'val', # yy
309 "k2": "v2" #
310}
311EOF
312echo status=$?
313
314json8 write (_reply)
315
316json read <<EOF
317{"key": "val"} # comment
318EOF
319echo status=$?
320## STDOUT:
321status=0
322{
323 "key": "val",
324 "k2": "v2"
325}
326status=1
327## END
328
329
330#### json write emits Unicode replacement char for binary data \yff
331
332json write ([3, "foo", $'-\xff\xfe---\xfd=']) > tmp.txt
333
334# Round trip it for good measure
335json read < tmp.txt
336
337json write (_reply)
338
339## STDOUT:
340[
341 3,
342 "foo",
343 "-��---�="
344]
345## END
346
347#### json8 write emits b'' strings for binary data \yff
348
349json8 write ([3, "foo", $'-\xff\xfe-\xfd='])
350
351## STDOUT:
352[
353 3,
354 "foo",
355 b'-\yff\yfe-\yfd='
356]
357## END
358
359
360#### json8 write bytes vs unicode string
361
362u=$'mu \u03bc \x01 \" \\ \b\f\n\r\t'
363u2=$'\x01\x1f' # this is a valid unicode string
364
365b=$'\xff' # this isn't valid unicode
366
367json8 write (u)
368json8 write (u2)
369
370json8 write (b)
371
372## STDOUT:
373"mu μ \u0001 \" \\ \b\f\n\r\t"
374"\u0001\u001f"
375b'\yff'
376## END
377
378#### JSON \/ escapes supported
379
380msg='"\/"'
381
382echo "$msg" | python3 -c 'import json, sys; print(json.load(sys.stdin))'
383
384echo "$msg" | json read
385echo reply=$_reply
386
387j8="b'\\/'"
388echo "$msg" | json read
389echo reply=$_reply
390
391
392## STDOUT:
393/
394reply=/
395reply=/
396## END
397
398#### JSON string can have unescaped ' and J8 string can have unescaped "
399
400json read <<EOF
401"'"
402EOF
403
404pp line (_reply)
405
406json8 read <<EOF
407u'"'
408EOF
409
410pp line (_reply)
411
412## STDOUT:
413(Str) "'"
414(Str) "\""
415## END
416
417
418#### J8 supports superfluous \" escapes, but JSON doesn't support \' escapes
419
420json8 read <<'EOF'
421b'\"'
422EOF
423echo reply=$_reply
424
425json8 read <<'EOF'
426b'\'\'\b\f\n\r\t\"\\'
427EOF
428pp line (_reply)
429
430# Suppress traceback
431python3 -c 'import json, sys; print(json.load(sys.stdin))' 2>/dev/null <<'EOF'
432"\'"
433EOF
434echo python3=$?
435
436json read <<'EOF'
437"\'"
438EOF
439echo json=$?
440
441## STDOUT:
442reply="
443(Str) "''\b\f\n\r\t\"\\"
444python3=1
445json=1
446## END
447
448#### Escaping uses \u0001 in "", but \u{1} in b''
449
450s1=$'\x01'
451s2=$'\x01\xff\x1f' # byte string
452
453json8 write (s1)
454json8 write (s2)
455
456## STDOUT:
457"\u0001"
458b'\u{1}\yff\u{1f}'
459## END
460
461
462#### json8 read
463
464echo '{ }' | json8 read
465pp line (_reply)
466
467echo '[ ]' | json8 read
468pp line (_reply)
469
470echo '[42]' | json8 read
471pp line (_reply)
472
473echo '[true, false]' | json8 read
474pp line (_reply)
475
476echo '{"k": "v"}' | json8 read
477pp line (_reply)
478
479echo '{"k": null}' | json8 read
480pp line (_reply)
481
482echo '{"k": 1, "k2": 2}' | json8 read
483pp line (_reply)
484
485echo "{u'k': {b'k2': null}}" | json8 read
486pp line (_reply)
487
488echo '{"k": {"k2": "v2"}, "k3": "backslash \\ \" \n line 2 \u03bc "}' | json8 read
489pp line (_reply)
490
491json8 read (&x) <<'EOF'
492{u'k': {u'k2': u'v2'}, u'k3': u'backslash \\ \" \n line 2 \u{3bc} '}
493EOF
494pp line (x)
495
496## STDOUT:
497(Dict) {}
498(List) []
499(List) [42]
500(List) [true,false]
501(Dict) {"k":"v"}
502(Dict) {"k":null}
503(Dict) {"k":1,"k2":2}
504(Dict) {"k":{"k2":null}}
505(Dict) {"k":{"k2":"v2"},"k3":"backslash \\ \" \n line 2 μ "}
506(Dict) {"k":{"k2":"v2"},"k3":"backslash \\ \" \n line 2 μ "}
507## END
508
509#### json8 round trip
510
511var obj = [42, 1.5, null, true, "hi", b'\yff\yfe\b\n""']
512
513json8 write --pretty=F (obj) > j
514
515cat j
516
517json8 read < j
518
519json8 write (_reply)
520
521## STDOUT:
522[42,1.5,null,true,"hi",b'\yff\yfe\b\n""']
523[
524 42,
525 1.5,
526 null,
527 true,
528 "hi",
529 b'\yff\yfe\b\n""'
530]
531## END
532
533#### json round trip (regression)
534
535var d = {
536 short: '-v', long: '--verbose', type: null, default: '', help: 'Enable verbose logging'
537}
538
539json write (d) | json read
540
541pp line (_reply)
542
543## STDOUT:
544(Dict) {"short":"-v","long":"--verbose","type":null,"default":"","help":"Enable verbose logging"}
545## END
546
547#### round trip: decode surrogate pair and encode
548
549var j = r'"\ud83e\udd26"'
550echo $j | json read (&c1)
551
552json write (c1)
553
554var j = r'"\uD83E\uDD26"'
555echo $j | json read (&c2)
556
557json write (c2)
558
559# Not a surrogate pair
560var j = r'"\u0001\u0002"'
561echo $j | json read (&c3)
562
563json write (c3)
564
565var j = r'"\u0100\u0101\u0102"'
566echo $j | json read (&c4)
567
568json write (c4)
569
570## STDOUT:
571"🤦"
572"🤦"
573"\u0001\u0002"
574"ĀāĂ"
575## END
576
577#### round trip: decode surrogate half and encode
578
579# TODO: Weird Python allows this to be decoded, but I think the Bjoern state
580# machine will not!
581
582shopt -s ysh:upgrade
583
584for j in '"\ud83e"' '"\udd26"' {
585 var s = fromJson(j)
586 pp line (s)
587
588 # TODO: modify DFA to return the code point in the surrogate range, and
589 # print it in JSON mode
590 # j8 mode could possibly use byte strings
591 json write (s)
592}
593
594## STDOUT:
595(Str) b'\ya0\ybe'
596"\ud83e"
597(Str) b'\yb4\ya6'
598"\udd26"
599## END
600
601#### toJson() toJson8() - TODO: test difference
602
603var obj = [42, 1.5, null, true, "hi"]
604
605echo $[toJson(obj)]
606echo $[toJson8(obj)]
607
608## STDOUT:
609[42,1.5,null,true,"hi"]
610[42,1.5,null,true,"hi"]
611## END
612
613#### fromJson() fromJson8() - TODO: test difference
614
615var message ='[42,1.5,null,true,"hi"]'
616
617pp line (fromJson(message))
618pp line (fromJson8(message))
619
620## STDOUT:
621(List) [42,1.5,null,true,"hi"]
622(List) [42,1.5,null,true,"hi"]
623## END
624
625#### User can handle errors - toJson() toJson8()
626shopt -s ysh:upgrade
627
628var obj = []
629call obj->append(obj)
630
631try {
632 echo $[toJson(obj)]
633}
634echo status=$_status
635echo "encode error $[_error.message]" | sed 's/0x[a-f0-9]\+/(object id)/'
636
637try { # use different style
638 echo $[toJson8( /d+/ )]
639}
640echo status=$_status
641echo "encode error $[_error.message]"
642
643# This makes the interpreter fail with a message
644echo $[toJson(obj)]
645
646## status: 4
647## STDOUT:
648status=4
649encode error Can't encode List (object id) in object cycle
650status=4
651encode error Can't serialize object of type Eggex
652## END
653
654#### User can handle errors - fromJson() fromJson8()
655shopt -s ysh:upgrade
656
657var message ='[42,1.5,null,true,"hi"'
658
659try {
660 var obj = fromJson(message)
661}
662echo status=$_status
663echo "decode error $[_error.message]" | egrep -o '.*Expected.*RBracket'
664
665try {
666 var obj = fromJson8(message)
667}
668echo status=$_status
669echo "decode error $[_error.message]" | egrep -o '.*Expected.*RBracket'
670
671try {
672 var obj = fromJson('[+]')
673}
674echo "positions $[_error.start_pos] - $[_error.end_pos]"
675
676# This makes the interpreter fail with a message
677var obj = fromJson(message)
678
679## status: 4
680## STDOUT:
681status=4
682decode error Expected Id.J8_RBracket
683status=4
684decode error Expected Id.J8_RBracket
685positions 1 - 2
686## END
687
688
689#### ASCII control chars can't appear literally in messages
690shopt -s ysh:upgrade
691
692var message=$'"\x01"'
693#echo $message | od -c
694
695try {
696 var obj = fromJson(message)
697}
698echo status=$_status
699echo "$[_error.message]" | egrep -o 'ASCII control chars'
700
701## STDOUT:
702status=4
703ASCII control chars
704## END
705
706
707#### \yff can't appear in u'' code strings (command)
708
709shopt -s ysh:upgrade
710
711echo -n b'\yfd' | od -A n -t x1
712echo -n u'\yfd' | od -A n -t x1
713
714## status: 2
715## STDOUT:
716 fd
717## END
718
719#### \yff can't appear in u'' code strings (expr)
720
721var x = b'\yfe'
722write -n -- $x | od -A n -t x1
723
724var x = u'\yfe'
725write -n -- $x | od -A n -t x1
726
727## status: 2
728## STDOUT:
729 fe
730## END
731
732#### \yff can't appear in u'' multiline code strings
733
734shopt -s ysh:upgrade
735
736echo -n b'''\yfc''' | od -A n -t x1
737echo -n u'''\yfd''' | od -A n -t x1
738
739## status: 2
740## STDOUT:
741 fc
742## END
743
744#### \yff can't appear in u'' data strings
745
746#shopt -s ysh:upgrade
747
748json8 read (&b) <<'EOF'
749b'\yfe'
750EOF
751pp line (b)
752
753json8 read (&u) <<'EOF'
754u'\yfe'
755EOF
756pp line (u) # undefined
757
758## status: 1
759## STDOUT:
760(Str) b'\yfe'
761## END
762
763#### \u{dc00} can't be in surrogate range in code (command)
764
765shopt -s ysh:upgrade
766
767echo -n u'\u{dc00}' | od -A n -t x1
768
769## status: 2
770## STDOUT:
771## END
772
773#### \u{dc00} can't be in surrogate range in code (expr)
774
775shopt -s ysh:upgrade
776
777var x = u'\u{dc00}'
778echo $x | od -A n -t x1
779
780## status: 2
781## STDOUT:
782## END
783
784#### \u{dc00} can't be in surrogate range in data
785
786json8 read <<'EOF'
787["long string", u'hello \u{d7ff}', "other"]
788EOF
789echo status=$?
790
791json8 read <<'EOF'
792["long string", u'hello \u{d800}', "other"]
793EOF
794echo status=$?
795
796json8 read <<'EOF'
797["long string", u'hello \u{dfff}', "other"]
798EOF
799echo status=$?
800
801json8 read <<'EOF'
802["long string", u'hello \u{e000}', "other"]
803EOF
804echo status=$?
805
806
807## STDOUT:
808status=0
809status=1
810status=1
811status=0
812## END
813
814
815
816#### Inf and NaN can't be encoded or decoded
817
818# This works in Python, should probably support it
819
820var n = float("NaN")
821var i = float("inf")
822
823pp line (n)
824pp line (i)
825
826json dump (n)
827json dump (i)
828
829## status: 2
830## STDOUT:
831## END
832
833#### Invalid UTF-8 in JSON is rejected
834
835echo $'"\xff"' | json read
836echo status=$?
837
838echo $'"\xff"' | json8 read
839echo status=$?
840
841echo $'\xff' | json read
842echo status=$?
843
844echo $'\xff' | json8 read
845echo status=$?
846
847## STDOUT:
848status=1
849status=1
850status=1
851status=1
852## END
853
854#### Invalid JSON in J8 is rejected
855
856json8 read <<EOF
857b'$(echo -e -n '\xff')'
858EOF
859echo status=$?
860
861json8 read <<EOF
862u'$(echo -e -n '\xff')'
863EOF
864echo status=$?
865
866## STDOUT:
867status=1
868status=1
869## END
870
871#### '' means the same thing as u''
872
873echo "''" | json8 read
874pp line (_reply)
875
876echo "'\u{3bc}'" | json8 read
877pp line (_reply)
878
879echo "'\yff'" | json8 read
880echo status=$?
881
882## STDOUT:
883(Str) ""
884(Str) "μ"
885status=1
886## END
887
888#### decode deeply nested structure (stack overflow)
889
890shopt -s ysh:upgrade
891
892proc pairs(n) {
893 var m = int(n) # TODO: 1 .. n should auto-convert?
894
895 for i in (1 .. m) {
896 write -n -- '['
897 }
898 for i in (1 .. m) {
899 write -n -- ']'
900 }
901}
902
903# This is all Python can handle; C++ can handle more
904msg=$(pairs 50)
905
906#echo $msg
907
908echo "$msg" | json read
909pp line (_reply)
910echo len=$[len(_reply)]
911
912## STDOUT:
913(List) [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
914len=1
915## END
916
917#### decode integer larger than 2^32
918
919json=$(( 1 << 33 ))
920echo $json
921
922echo $json | json read
923pp line (_reply)
924
925## STDOUT:
9268589934592
927(Int) 8589934592
928## END
929
930#### round trip: read/write with ysh
931
932var file = "$REPO_ROOT/spec/testdata/bug.json"
933#cat $file
934cat $file | json read (&cfg)
935json write (cfg) > ysh-json
936
937diff -u $file ysh-json
938echo diff=$?
939
940## STDOUT:
941diff=0
942## END
943
944#### round trip: read/write with ysh, read/write with Python 3 (bug regression)
945
946var file = "$REPO_ROOT/spec/testdata/bug.json"
947#cat $file
948cat $file | json read (&cfg)
949json write (cfg) > ysh-json
950
951cat ysh-json | python3 -c \
952 'import json, sys; obj = json.load(sys.stdin); json.dump(obj, sys.stdout, indent=2); print()' \
953 > py-json
954
955diff -u $file py-json
956echo diff=$?
957
958## STDOUT:
959diff=0
960## END
961
962#### Encoding bytes that don't hit UTF8_REJECT immediately (bug fix)
963
964var x = $'\xce'
965json8 write (x)
966declare -p x
967echo
968
969var y = $'\xbc'
970json8 write (y)
971declare -p y
972echo
973
974var z = $'\xf0\x9f\xa4\xff'
975json8 write (z)
976declare -p z
977
978## STDOUT:
979b'\yce'
980declare -- x=$'\xce'
981
982b'\ybc'
983declare -- y=$'\xbc'
984
985b'\yf0\y9f\ya4\yff'
986declare -- z=$'\xf0\x9f\xa4\xff'
987## END
988
989#### NIL8 token in JSON / JSON8
990
991echo "(" | json read
992echo status=$?
993
994echo ")" | json8 read
995echo status=$?
996
997## STDOUT:
998status=1
999status=1
1000## END