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

1080 lines, 289 significant
1## oils_failures_allowed: 1
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 (['foo.cc', 'foo.h'], space=0)
30## STDOUT:
31[
32 "foo.cc",
33 "foo.h"
34]
35["foo.cc","foo.h"]
36## END
37
38#### json write Dict
39json write ({k: 'v', k2: [4, 5]})
40
41json write ([{k: 'v', k2: 'v2'}, {}])
42
43## STDOUT:
44{
45 "k": "v",
46 "k2": [
47 4,
48 5
49 ]
50}
51[
52 {
53 "k": "v",
54 "k2": "v2"
55 },
56 {}
57]
58## END
59
60#### json write space=0, space=4
61shopt --set parse_proc
62
63var mydict = {name: "bob", age: 30}
64
65json write (mydict, space=0)
66json write (mydict, space=4)
67## STDOUT:
68{"name":"bob","age":30}
69{
70 "name": "bob",
71 "age": 30
72}
73## END
74
75#### json write in command sub
76shopt -s oil:all # for echo
77var mydict = {name: "bob", age: 30}
78json write (mydict)
79var x = $(json write (mydict))
80echo $x
81## STDOUT:
82{
83 "name": "bob",
84 "age": 30
85}
86{
87 "name": "bob",
88 "age": 30
89}
90## END
91
92#### json read passed invalid args
93
94# EOF
95json read
96echo status=$?
97
98json read 'z z'
99echo status=$?
100
101json read a b c
102echo status=$?
103
104## STDOUT:
105status=1
106status=2
107status=2
108## END
109
110#### json read uses $_reply var
111
112# space before true
113echo ' true' | json read
114json write (_reply)
115
116## STDOUT:
117true
118## END
119
120#### json read then json write
121
122# BUG with space before true
123echo '{"name": "bob", "age": 42, "ok": true}' | json read
124json write (_reply)
125
126echo '{"name": "bob", "age": 42, "ok":true}' | json read
127json write (_reply)
128
129echo '{"name": {}, "age": {}, "x":-1, "y": -0}' | json read
130json write (_reply)
131
132## STDOUT:
133{
134 "name": "bob",
135 "age": 42,
136 "ok": true
137}
138{
139 "name": "bob",
140 "age": 42,
141 "ok": true
142}
143{
144 "name": {},
145 "age": {},
146 "x": -1,
147 "y": 0
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 ([1,2,3], space=0)
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 (top, space=0)
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 accepts j"" prefix, but json doesn't
348
349var msg = r'j"foo\nbar"'
350
351echo "$msg" | json read
352echo json=$?
353echo
354
355echo "$msg" | json8 read
356echo json8=$?
357pp line (_reply)
358echo
359
360var msg = r'j"\u0041"'
361echo "$msg" | json8 read
362echo json8=$?
363pp line (_reply)
364
365
366## STDOUT:
367json=1
368
369json8=0
370(Str) "foo\nbar"
371
372json8=0
373(Str) "A"
374## END
375
376#### j"" prefix not accepted in YSH (could be added later)
377
378shopt -s ysh:all
379
380# denied by YSH
381# echo j"\u{7f}"
382
383var s = j"\u{7f}"
384
385## status: 2
386## STDOUT:
387## END
388
389
390#### json8 write emits b'' strings for binary data \yff
391
392json8 write ([3, "foo", $'-\xff\xfe-\xfd='])
393
394## STDOUT:
395[
396 3,
397 "foo",
398 b'-\yff\yfe-\yfd='
399]
400## END
401
402
403#### json8 write bytes vs unicode string
404
405u=$'mu \u03bc \x01 \" \\ \b\f\n\r\t'
406u2=$'\x01\x1f' # this is a valid unicode string
407
408b=$'\xff' # this isn't valid unicode
409
410json8 write (u)
411json8 write (u2)
412
413json8 write (b)
414
415## STDOUT:
416"mu μ \u0001 \" \\ \b\f\n\r\t"
417"\u0001\u001f"
418b'\yff'
419## END
420
421#### JSON \/ escapes supported
422
423msg='"\/"'
424
425echo "$msg" | python3 -c 'import json, sys; print(json.load(sys.stdin))'
426
427echo "$msg" | json read
428echo reply=$_reply
429
430j8="b'\\/'"
431echo "$msg" | json read
432echo reply=$_reply
433
434
435## STDOUT:
436/
437reply=/
438reply=/
439## END
440
441#### JSON string can have unescaped ' and J8 string can have unescaped "
442
443json read <<EOF
444"'"
445EOF
446
447pp line (_reply)
448
449json8 read <<EOF
450u'"'
451EOF
452
453pp line (_reply)
454
455## STDOUT:
456(Str) "'"
457(Str) "\""
458## END
459
460
461#### J8 supports superfluous \" escapes, but JSON doesn't support \' escapes
462
463json8 read <<'EOF'
464b'\"'
465EOF
466echo reply=$_reply
467
468json8 read <<'EOF'
469b'\'\'\b\f\n\r\t\"\\'
470EOF
471pp line (_reply)
472
473# Suppress traceback
474python3 -c 'import json, sys; print(json.load(sys.stdin))' 2>/dev/null <<'EOF'
475"\'"
476EOF
477echo python3=$?
478
479json read <<'EOF'
480"\'"
481EOF
482echo json=$?
483
484## STDOUT:
485reply="
486(Str) "''\b\f\n\r\t\"\\"
487python3=1
488json=1
489## END
490
491#### Escaping uses \u0001 in "", but \u{1} in b''
492
493s1=$'\x01'
494s2=$'\x01\xff\x1f' # byte string
495
496json8 write (s1)
497json8 write (s2)
498
499## STDOUT:
500"\u0001"
501b'\u{1}\yff\u{1f}'
502## END
503
504
505#### json8 read
506
507echo '{ }' | json8 read
508pp line (_reply)
509
510echo '[ ]' | json8 read
511pp line (_reply)
512
513echo '[42]' | json8 read
514pp line (_reply)
515
516echo '[true, false]' | json8 read
517pp line (_reply)
518
519echo '{"k": "v"}' | json8 read
520pp line (_reply)
521
522echo '{"k": null}' | json8 read
523pp line (_reply)
524
525echo '{"k": 1, "k2": 2}' | json8 read
526pp line (_reply)
527
528echo "{u'k': {b'k2': null}}" | json8 read
529pp line (_reply)
530
531echo '{"k": {"k2": "v2"}, "k3": "backslash \\ \" \n line 2 \u03bc "}' | json8 read
532pp line (_reply)
533
534json8 read (&x) <<'EOF'
535{u'k': {u'k2': u'v2'}, u'k3': u'backslash \\ \" \n line 2 \u{3bc} '}
536EOF
537pp line (x)
538
539## STDOUT:
540(Dict) {}
541(List) []
542(List) [42]
543(List) [true,false]
544(Dict) {"k":"v"}
545(Dict) {"k":null}
546(Dict) {"k":1,"k2":2}
547(Dict) {"k":{"k2":null}}
548(Dict) {"k":{"k2":"v2"},"k3":"backslash \\ \" \n line 2 μ "}
549(Dict) {"k":{"k2":"v2"},"k3":"backslash \\ \" \n line 2 μ "}
550## END
551
552#### json8 round trip
553
554var obj = [42, 1.5, null, true, "hi", b'\yff\yfe\b\n""']
555
556json8 write (obj, space=0) > j
557
558cat j
559
560json8 read < j
561
562json8 write (_reply)
563
564## STDOUT:
565[42,1.5,null,true,"hi",b'\yff\yfe\b\n""']
566[
567 42,
568 1.5,
569 null,
570 true,
571 "hi",
572 b'\yff\yfe\b\n""'
573]
574## END
575
576#### json round trip (regression)
577
578var d = {
579 short: '-v', long: '--verbose', type: null, default: '', help: 'Enable verbose logging'
580}
581
582json write (d) | json read
583
584pp line (_reply)
585
586## STDOUT:
587(Dict) {"short":"-v","long":"--verbose","type":null,"default":"","help":"Enable verbose logging"}
588## END
589
590#### round trip: decode surrogate pair and encode
591
592var j = r'"\ud83e\udd26"'
593echo $j | json read (&c1)
594
595json write (c1)
596
597var j = r'"\uD83E\uDD26"'
598echo $j | json read (&c2)
599
600json write (c2)
601
602# Not a surrogate pair
603var j = r'"\u0001\u0002"'
604echo $j | json read (&c3)
605
606json write (c3)
607
608var j = r'"\u0100\u0101\u0102"'
609echo $j | json read (&c4)
610
611json write (c4)
612
613## STDOUT:
614"🤦"
615"🤦"
616"\u0001\u0002"
617"ĀāĂ"
618## END
619
620#### round trip: decode surrogate half and encode
621
622shopt -s ysh:upgrade
623
624for j in '"\ud83e"' '"\udd26"' {
625 var s = fromJson(j)
626 write -- "$j"
627 pp line (s)
628
629 write -n 'json '; json write (s)
630
631 write -n 'json8 '; json8 write (s)
632
633 echo
634}
635
636## STDOUT:
637"\ud83e"
638(Str) b'\yed\ya0\ybe'
639json "\ud83e"
640json8 b'\yed\ya0\ybe'
641
642"\udd26"
643(Str) b'\yed\yb4\ya6'
644json "\udd26"
645json8 b'\yed\yb4\ya6'
646
647## END
648
649#### toJson() toJson8()
650
651var obj = [42, 1.5, null, true, "hi", b'\yf0']
652
653echo $[toJson(obj)]
654echo $[toJson8(obj)]
655
656var obj2 = [3, 4]
657echo $[toJson(obj2, space=0)] # same as the default
658echo $[toJson8(obj2, space=0)]
659
660echo $[toJson(obj2, space=2)]
661echo $[toJson8(obj2, space=2)]
662
663# fully specify this behavior
664echo $[toJson(obj2, space=-2)]
665echo $[toJson8(obj2, space=-2)]
666
667## STDOUT:
668[42,1.5,null,true,"hi",""]
669[42,1.5,null,true,"hi",b'\yf0']
670[3,4]
671[3,4]
672[
673 3,
674 4
675]
676[
677 3,
678 4
679]
680[3,4]
681[3,4]
682## END
683
684#### fromJson() fromJson8()
685
686var m1 = '[42,1.5,null,true,"hi"]'
687
688# JSON8 message
689var m2 = '[42,1.5,null,true,"hi",' ++ "u''" ++ ']'
690
691pp line (fromJson8(m1))
692pp line (fromJson(m1))
693
694pp line (fromJson8(m2))
695pp line (fromJson(m2)) # fails
696
697## status: 4
698## STDOUT:
699(List) [42,1.5,null,true,"hi"]
700(List) [42,1.5,null,true,"hi"]
701(List) [42,1.5,null,true,"hi",""]
702## END
703
704#### User can handle errors - toJson() toJson8()
705shopt -s ysh:upgrade
706
707var obj = []
708call obj->append(obj)
709
710try {
711 echo $[toJson(obj)]
712}
713echo status=$_status
714echo "encode error $[_error.message]" | sed 's/0x[a-f0-9]\+/(object id)/'
715
716try { # use different style
717 echo $[toJson8( /d+/ )]
718}
719echo status=$_status
720echo "encode error $[_error.message]"
721
722# This makes the interpreter fail with a message
723echo $[toJson(obj)]
724
725## status: 4
726## STDOUT:
727status=4
728encode error Can't encode List (object id) in object cycle
729status=4
730encode error Can't serialize object of type Eggex
731## END
732
733#### User can handle errors - fromJson() fromJson8()
734shopt -s ysh:upgrade
735
736var message ='[42,1.5,null,true,"hi"'
737
738try {
739 var obj = fromJson(message)
740}
741echo status=$_status
742echo "decode error $[_error.message]" | egrep -o '.*Expected.*RBracket'
743
744try {
745 var obj = fromJson8(message)
746}
747echo status=$_status
748echo "decode error $[_error.message]" | egrep -o '.*Expected.*RBracket'
749
750try {
751 var obj = fromJson('[+]')
752}
753echo "positions $[_error.start_pos] - $[_error.end_pos]"
754
755# This makes the interpreter fail with a message
756var obj = fromJson(message)
757
758## status: 4
759## STDOUT:
760status=4
761decode error Expected Id.J8_RBracket
762status=4
763decode error Expected Id.J8_RBracket
764positions 1 - 2
765## END
766
767
768#### ASCII control chars can't appear literally in messages
769shopt -s ysh:upgrade
770
771var message=$'"\x01"'
772#echo $message | od -c
773
774try {
775 var obj = fromJson(message)
776}
777echo status=$_status
778echo "$[_error.message]" | egrep -o 'ASCII control chars'
779
780## STDOUT:
781status=4
782ASCII control chars
783## END
784
785
786#### \yff can't appear in u'' code strings (command)
787
788shopt -s ysh:upgrade
789
790echo -n b'\yfd' | od -A n -t x1
791echo -n u'\yfd' | od -A n -t x1
792
793## status: 2
794## STDOUT:
795 fd
796## END
797
798#### \yff can't appear in u'' code strings (expr)
799
800var x = b'\yfe'
801write -n -- $x | od -A n -t x1
802
803var x = u'\yfe'
804write -n -- $x | od -A n -t x1
805
806## status: 2
807## STDOUT:
808 fe
809## END
810
811#### \yff can't appear in u'' multiline code strings
812
813shopt -s ysh:upgrade
814
815echo -n b'''\yfc''' | od -A n -t x1
816echo -n u'''\yfd''' | od -A n -t x1
817
818## status: 2
819## STDOUT:
820 fc
821## END
822
823#### \yff can't appear in u'' data strings
824
825#shopt -s ysh:upgrade
826
827json8 read (&b) <<'EOF'
828b'\yfe'
829EOF
830pp line (b)
831
832json8 read (&u) <<'EOF'
833u'\yfe'
834EOF
835pp line (u) # undefined
836
837## status: 1
838## STDOUT:
839(Str) b'\yfe'
840## END
841
842#### \u{dc00} can't be in surrogate range in code (command)
843
844shopt -s ysh:upgrade
845
846echo -n u'\u{dc00}' | od -A n -t x1
847
848## status: 2
849## STDOUT:
850## END
851
852#### \u{dc00} can't be in surrogate range in code (expr)
853
854shopt -s ysh:upgrade
855
856var x = u'\u{dc00}'
857echo $x | od -A n -t x1
858
859## status: 2
860## STDOUT:
861## END
862
863#### \u{dc00} can't be in surrogate range in data
864
865json8 read <<'EOF'
866["long string", u'hello \u{d7ff}', "other"]
867EOF
868echo status=$?
869
870json8 read <<'EOF'
871["long string", u'hello \u{d800}', "other"]
872EOF
873echo status=$?
874
875json8 read <<'EOF'
876["long string", u'hello \u{dfff}', "other"]
877EOF
878echo status=$?
879
880json8 read <<'EOF'
881["long string", u'hello \u{e000}', "other"]
882EOF
883echo status=$?
884
885
886## STDOUT:
887status=0
888status=1
889status=1
890status=0
891## END
892
893
894
895#### Inf and NaN can't be encoded or decoded
896
897# This works in Python, should probably support it
898
899var n = float("NaN")
900var i = float("inf")
901
902pp line (n)
903pp line (i)
904
905json dump (n)
906json dump (i)
907
908## status: 2
909## STDOUT:
910## END
911
912#### Invalid UTF-8 in JSON is rejected
913
914echo $'"\xff"' | json read
915echo status=$?
916
917echo $'"\xff"' | json8 read
918echo status=$?
919
920echo $'\xff' | json read
921echo status=$?
922
923echo $'\xff' | json8 read
924echo status=$?
925
926## STDOUT:
927status=1
928status=1
929status=1
930status=1
931## END
932
933#### Invalid JSON in J8 is rejected
934
935json8 read <<EOF
936b'$(echo -e -n '\xff')'
937EOF
938echo status=$?
939
940json8 read <<EOF
941u'$(echo -e -n '\xff')'
942EOF
943echo status=$?
944
945## STDOUT:
946status=1
947status=1
948## END
949
950#### '' means the same thing as u''
951
952echo "''" | json8 read
953pp line (_reply)
954
955echo "'\u{3bc}'" | json8 read
956pp line (_reply)
957
958echo "'\yff'" | json8 read
959echo status=$?
960
961## STDOUT:
962(Str) ""
963(Str) "μ"
964status=1
965## END
966
967#### decode deeply nested structure (stack overflow)
968
969shopt -s ysh:upgrade
970
971proc pairs(n) {
972 var m = int(n) # TODO: 1 .. n should auto-convert?
973
974 for i in (1 .. m) {
975 write -n -- '['
976 }
977 for i in (1 .. m) {
978 write -n -- ']'
979 }
980}
981
982# This is all Python can handle; C++ can handle more
983msg=$(pairs 50)
984
985#echo $msg
986
987echo "$msg" | json read
988pp line (_reply)
989echo len=$[len(_reply)]
990
991## STDOUT:
992(List) [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
993len=1
994## END
995
996#### decode integer larger than 2^32
997
998json=$(( 1 << 33 ))
999echo $json
1000
1001echo $json | json read
1002pp line (_reply)
1003
1004## STDOUT:
10058589934592
1006(Int) 8589934592
1007## END
1008
1009#### round trip: read/write with ysh
1010
1011var file = "$REPO_ROOT/spec/testdata/bug.json"
1012#cat $file
1013cat $file | json read (&cfg)
1014json write (cfg) > ysh-json
1015
1016diff -u $file ysh-json
1017echo diff=$?
1018
1019## STDOUT:
1020diff=0
1021## END
1022
1023#### round trip: read/write with ysh, read/write with Python 3 (bug regression)
1024
1025var file = "$REPO_ROOT/spec/testdata/bug.json"
1026#cat $file
1027cat $file | json read (&cfg)
1028json write (cfg) > ysh-json
1029
1030cat ysh-json | python3 -c \
1031 'import json, sys; obj = json.load(sys.stdin); json.dump(obj, sys.stdout, indent=2); print()' \
1032 > py-json
1033
1034diff -u $file py-json
1035echo diff=$?
1036
1037## STDOUT:
1038diff=0
1039## END
1040
1041#### Encoding bytes that don't hit UTF8_REJECT immediately (bug fix)
1042
1043var x = $'\xce'
1044json8 write (x)
1045declare -p x
1046echo
1047
1048var y = $'\xbc'
1049json8 write (y)
1050declare -p y
1051echo
1052
1053var z = $'\xf0\x9f\xa4\xff'
1054json8 write (z)
1055declare -p z
1056
1057## STDOUT:
1058b'\yce'
1059declare -- x=$'\xce'
1060
1061b'\ybc'
1062declare -- y=$'\xbc'
1063
1064b'\yf0\y9f\ya4\yff'
1065declare -- z=$'\xf0\x9f\xa4\xff'
1066## END
1067
1068#### NIL8 token in JSON / JSON8
1069
1070echo "(" | json read
1071echo status=$?
1072
1073echo ")" | json8 read
1074echo status=$?
1075
1076## STDOUT:
1077status=1
1078status=1
1079## END
1080