2
2
3
3
import java .util .ArrayList ;
4
4
import java .util .HashMap ;
5
+ import java .util .Iterator ;
5
6
import java .util .List ;
7
+ import java .util .Map ;
8
+ import java .util .Map .Entry ;
6
9
7
10
import convex .core .cvm .Address ;
8
11
import convex .core .data .ACell ;
12
+ import convex .core .data .ACollection ;
9
13
import convex .core .data .AMap ;
10
14
import convex .core .data .ASequence ;
11
15
import convex .core .data .AString ;
16
+ import convex .core .data .ASymbolic ;
17
+ import convex .core .data .Blob ;
12
18
import convex .core .data .Keyword ;
13
19
import convex .core .data .MapEntry ;
20
+ import convex .core .data .StringShort ;
14
21
import convex .core .data .Strings ;
15
22
import convex .core .data .prim .CVMBool ;
16
23
import convex .core .data .prim .CVMChar ;
@@ -62,7 +69,7 @@ public static <T> T json(ACell o) {
62
69
}
63
70
return (T ) list ;
64
71
}
65
-
72
+
66
73
return (T ) o .toString ();
67
74
}
68
75
@@ -79,6 +86,21 @@ public static String jsonKey(ACell k) {
79
86
return ((Keyword ) k ).getName ().toString ();
80
87
return RT .toString (k );
81
88
}
89
+
90
+ /**
91
+ * Gets a String from a value suitable for use as a JSON map key
92
+ *
93
+ * @param o Value to convert to a JSON key
94
+ * @return String usable as JSON key
95
+ */
96
+ public static String jsonKey (Object o ) {
97
+ if (o instanceof ACell cell )
98
+ return jsonKey (cell );
99
+
100
+ if (o instanceof String s ) return s ;
101
+
102
+ throw new IllegalArgumentException ("Invalid yupe for JSON key: " +Utils .getClassName (o ));
103
+ }
82
104
83
105
/**
84
106
* Converts a CVM Map to a JSON representation
@@ -98,30 +120,224 @@ public static HashMap<String, Object> jsonMap(AMap<?, ?> m) {
98
120
}
99
121
return hm ;
100
122
}
101
-
123
+
124
+ /**
125
+ * Convert any object to JSON
126
+ *
127
+ * @param value Value to convert to JSON, may be Java or CVM structure
128
+ * @return JSON String
129
+ */
102
130
public static String toString (Object value ) {
103
131
return toCVMString (value ).toString ();
104
132
}
105
133
106
- private static AString toCVMString (Object value ) {
107
- BlobBuilder bb =new BlobBuilder ();
108
- appendCVMString (bb ,value );
134
+ /**
135
+ * Convert any object to JSON
136
+ *
137
+ * @param value Value to convert to JSON, may be Java or CVM structure
138
+ * @return CVM String containing valid JSON
139
+ */
140
+ public static AString toCVMString (Object value ) {
141
+ BlobBuilder bb = new BlobBuilder ();
142
+ appendCVMString (bb , value );
109
143
return Strings .create (bb .toBlob ());
110
144
}
145
+
146
+ private static void appendCVMString (BlobBuilder bb , Object value ) {
147
+ if (value == null ) {
148
+ bb .append (Strings .NULL );
149
+ return ;
150
+ }
151
+
152
+ if (value instanceof ACell cell ) {
153
+ appendCVMString (bb ,cell );
154
+ return ;
155
+ }
111
156
112
- private static void appendCVMString (BlobBuilder bb ,Object value ) {
113
- if (value ==null ) bb .append (Strings .NULL );
114
157
158
+ if (value instanceof Map mv ) {
159
+ bb .append ('{' );
160
+ int i =0 ;
161
+ @ SuppressWarnings ("unchecked" )
162
+ Iterator <Map .Entry <Object ,Object >> it = mv .entrySet ().iterator ();
163
+ while (it .hasNext ()) {
164
+ Entry <Object , Object > me = it .next ();
165
+ if (i >0 ) bb .append ("\n " );
166
+ appendCVMString (bb , jsonKey (me .getKey ()));
167
+ bb .append (':' );
168
+ bb .append (' ' );
169
+ appendCVMString (bb , me .getValue ());
170
+
171
+ i += 1 ;
172
+ }
173
+
174
+ bb .append ('}' );
175
+ return ;
176
+ }
177
+
178
+ // This catches Java lists
115
179
if (value instanceof List lv ) {
116
180
bb .append ('[' );
117
- int n =lv .size ();
118
- for (int i =0 ; i <n ; i ++) {
119
- appendCVMString (bb ,lv .get (i ));
181
+ int n = lv .size ();
182
+ for (int i = 0 ; i < n ; i ++) {
183
+ if (i >0 ) bb .append (' ' );
184
+ appendCVMString (bb , lv .get (i ));
185
+ }
186
+ bb .append (']' );
187
+ return ;
188
+ }
189
+
190
+ if (value instanceof Boolean bv ) {
191
+ bb .append (bv ? Strings .TRUE : Strings .FALSE );
192
+ return ;
193
+ }
194
+
195
+ if (value instanceof String cs ) {
196
+ bb .append ('\"' );
197
+ appendCVMStringQuoted (bb , cs );
198
+ bb .append ('\"' );
199
+ return ;
200
+ }
201
+
202
+ if (value instanceof Number nv ) {
203
+ if (value instanceof Double dv ) {
204
+ if (Double .isFinite (dv )) {
205
+ bb .append (nv .toString ());
206
+ return ;
207
+ } else {
208
+ if (Double .isNaN (dv )) {
209
+ bb .append (JS_NAN );
210
+ } else {
211
+ if (dv <0 ) {
212
+ bb .append ('-' );
213
+ }
214
+ bb .append ("Infinity" );
215
+ }
216
+ }
217
+ return ;
218
+ }
219
+
220
+ bb .append (nv .toString ());
221
+ return ;
222
+ }
223
+
224
+ throw new IllegalArgumentException ("Can't print type as JSON: " +Utils .getClassName (value ));
225
+ }
226
+
227
+ // Specialised writing for CVM types
228
+ private static void appendCVMString (BlobBuilder bb , ACell value ) {
229
+ if (value == null ) {
230
+ bb .append (Strings .NULL );
231
+ return ;
232
+ }
233
+
234
+ if (value instanceof AString cs ) {
235
+ bb .append ('\"' );
236
+ appendCVMStringQuoted (bb , cs .toString ()); // TODO: can be faster
237
+ bb .append ('\"' );
238
+ return ;
239
+ }
240
+
241
+ if (value instanceof ASymbolic cs ) {
242
+ // Print as the symbolic name string
243
+ appendCVMString (bb , cs .getName ());
244
+ return ;
245
+ }
246
+
247
+ // CVM map special treatment
248
+ if (value instanceof AMap mv ) {
249
+ bb .append ('{' );
250
+ long n = mv .size ();
251
+ for (long i = 0 ; i < n ; i ++) {
252
+ if (i >0 ) bb .append (' ' );
253
+ MapEntry <?,?> me =mv .entryAt (i );
254
+ appendCVMString (bb , jsonKey (me .getKey ()));
255
+ bb .append (':' );
120
256
bb .append (' ' );
257
+ appendCVMString (bb , me .getValue ());
258
+ }
259
+ bb .append ('}' );
260
+ return ;
261
+ }
262
+
263
+ // Maps, Lists and Sets get printed as JSON arrays
264
+ if (value instanceof ACollection lv ) {
265
+ bb .append ('[' );
266
+ long n = lv .count ();
267
+ for (long i = 0 ; i < n ; i ++) {
268
+ if (i >0 ) bb .append (' ' );
269
+ appendCVMString (bb , lv .get (i ));
121
270
}
122
271
bb .append (']' );
272
+ return ;
273
+ }
274
+
275
+
276
+ if (value instanceof CVMLong nv ) {
277
+ appendCVMString (bb ,nv .longValue ());
278
+ return ;
123
279
}
124
280
281
+ if (value instanceof CVMDouble nv ) {
282
+ appendCVMString (bb ,nv .doubleValue ());
283
+ return ;
284
+ }
285
+
286
+ if (value instanceof CVMBool bv ) {
287
+ bb .append (bv .booleanValue () ? Strings .TRUE : Strings .FALSE );
288
+ return ;
289
+ }
290
+ }
291
+
292
+
293
+ private static void appendCVMStringQuoted (BlobBuilder bb , CharSequence cs ) {
294
+ int n = cs .length ();
295
+ for (int i = 0 ; i < n ; i ++) {
296
+ char c = cs .charAt (i );
297
+ AString rep = getReplacementString (c );
298
+ if (rep != null ) {
299
+ bb .append (rep );
300
+ } else {
301
+ bb .append (c );
302
+ }
303
+ }
304
+ }
305
+
306
+ private static final StringShort QUOTED_BACKSLASH = StringShort .create ("\\ \\ " );
307
+ private static final StringShort QUOTED_QUOTES = StringShort .create ("\\ \" " );
308
+ private static final StringShort QUOTED_NEWLINE = StringShort .create ("\\ n" );
309
+ private static final StringShort QUOTED_RETURN = StringShort .create ("\\ r" );
310
+ private static final StringShort QUOTED_TAB = StringShort .create ("\\ t" );
311
+
312
+ private static final StringShort JS_NAN = StringShort .create ("NaN" );
313
+
314
+ private static final char CONTROL_CHARS_END = 0x001f ; // Highest ASCII control character
315
+
316
+
317
+ private static AString getReplacementString (char c ) {
318
+ if (c == '\\' ) {
319
+ return QUOTED_BACKSLASH ;
320
+ }
321
+ if (c > '"' ) {
322
+ // anything above this is OK in a JSON String
323
+ return null ;
324
+ }
325
+ if (c == '"' ) {
326
+ return QUOTED_QUOTES ;
327
+ }
328
+ if (c > CONTROL_CHARS_END ) {
329
+ return null ;
330
+ }
331
+ if (c == '\n' ) {
332
+ return QUOTED_NEWLINE ;
333
+ }
334
+ if (c == '\r' ) {
335
+ return QUOTED_RETURN ;
336
+ }
337
+ if (c == '\t' ) {
338
+ return QUOTED_TAB ;
339
+ }
340
+ return StringShort .create (Blob .wrap (new byte [] { '\\' , 'u' , '0' , '0' , (byte ) Utils .toHexChar ((c >> 4 ) & 0x000f ), (byte ) Utils .toHexChar (c & 0x000f ) }));
125
341
}
126
342
127
343
}
0 commit comments