1
2/**
3	trait_json_serialize
4	Objects with this trait will be assumed to convert to json data
5	when its ->asString method is called
6*/
7define trait_json_serialize => trait {
8	require asString()
9}
10
11define json_serialize(e::bytes)::string => ('"' + (string(#e)->Replace(`\`, `\\`) & Replace('\"', '\\"') & Replace('\r', '\\r') & Replace('\n', '\\n') & Replace('\t', '\\t') & Replace('\f', '\\f') & Replace('\b', '\\b') &) + '"')
12define json_serialize(e::string)::string => ('"' + (string(#e)->Replace(`\`, `\\`) & Replace('\"', '\\"') & Replace('\r', '\\r') & Replace('\n', '\\n') & Replace('\t', '\\t') & Replace('\f', '\\f') & Replace('\b', '\\b') &) + '"')
13define json_serialize(e::json_literal)::string => (#e->asstring)
14define json_serialize(e::integer)::string => (#e->asstring)
15define json_serialize(e::decimal)::string => (#e->asstring)
16define json_serialize(e::boolean)::string => (#e->asstring)
17define json_serialize(e::null)::string => ('null')
18define json_serialize(e::date)::string => ('"' + #e->format(#e->gmt ? '%QT%TZ' | '%Q%T') + '"')
19/*
20define json_serialize(e::array)::string => {
21	local(output) = '';
22	local(delimit) = '';
23	#e->foreach => { #output += #delimit + json_serialize(#1); #delimit = ', '; }
24	return('[' + #output + ']');
25}
26define json_serialize(e::staticarray)::string => {
27	local(output) = '';
28	local(delimit) = '';
29	#e->foreach => { #output += #delimit + json_serialize(#1); #delimit = ', '; }
30	return('[' + #output + ']');
31}
32*/
33define json_serialize(e::trait_forEach)::string => {
34	local(output) = '';
35	local(delimit) = '';
36	#e->foreach => { #output += #delimit + json_serialize(#1); #delimit = ', '; }
37	return('[' + #output + ']');
38}
39define json_serialize(e::map)::string => {
40	local(output = with pr in #e->eachPair
41					select json_serialize(#pr->first->asString) + ': ' + json_serialize(#pr->second))
42	return '{' + #output->join(',') + '}'
43}
44define json_serialize(e::json_object)::string => {
45	local(output) = '';
46	local(delimit) = '';
47	#e->foreachpair => { #output += #delimit + #1->first + ': ' + json_serialize(#1->second); #delimit = ', '; }
48	return('{' + #output + '}');
49}
50define json_serialize(e::trait_json_serialize) => #e->asString
51define json_serialize(e::any)::string => json_serialize('<LassoNativeType>' + #e->serialize + '</LassoNativeType>')
52
53// Bil Corry fixes for decoding json
54define json_consume_string(ibytes::bytes) => {
55	local(obytes) = bytes;
56	local(temp) = 0;
57	while((#temp := #ibytes->export8bits) != 34);
58		#obytes->import8bits(#temp);
59		(#temp == 92) ? #obytes->import8bits(#ibytes->export8bits); // Escape \
60 	/while;
61	local(output = string(#obytes)->unescape)
62	//Replace('\\"', '\"') & Replace('\\r', '\r') & Replace('\\n', '\n') & Replace('\\t', '\t') & Replace('\\f', '\f') & Replace('\\b', '\b') &;
63	if(#output->BeginsWith('<LassoNativeType>') && #output->EndsWith('</LassoNativeType>'));
64		Protect;
65			return serialization_reader(xml(#output - '<LassoNativeType>' - '</LassoNativeType>'))->read
66		/Protect;
67	else( (#output->size == 16 or #output->size == 15) and regexp(`\d{8}T\d{6}Z?`, '', #output)->matches)
68		return date(#output, -Format=#output->size == 16?`yyyyMMdd'T'HHmmssZ`|`yyyyMMdd'T'HHmmss`)
69	/if
70	return #output
71}
72
73// Bil Corry fix + Ke fix
74define json_consume_token(ibytes::bytes, temp::integer) => {
75
76	local(obytes = bytes->import8bits(#temp) &,
77		delimit = array(9, 10, 13, 32, 44, 58, 93, 125)) // \t\r\n ,:]}
78
79	while(#delimit !>> (#temp := #ibytes->export8bits))
80		#obytes->import8bits(#temp)
81	/while
82
83	#temp == 125? // }
84		#ibytes->marker -= 1
85//============================================================================
86//	Is also end of token if end of array[]
87	#temp == 93? // ]
88		#ibytes->marker -= 1
89//............................................................................
90
91	local(output = string(#obytes))
92	#output == 'true'?
93		return true
94	#output == 'false'?
95		return false
96	#output == 'null'?
97		return null
98	string_IsNumeric(#output)?
99	return (#output >> '.')? decimal(#output) | integer(#output)
100
101	return #output
102}
103
104// Bil Corry fix
105define json_consume_array(ibytes::bytes)::array => {
106	Local(output) = array;
107	local(delimit) = array( 9, 10, 13, 32, 44); // \t\r\n ,
108	local(temp) = 0;
109	While((#temp := #ibytes->export8bits) != 93); // ]
110		If(#delimit >> #temp);
111			// Discard whitespace
112		Else(#temp == 34); // "
113			#output->insert(json_consume_string(#ibytes));
114		Else(#temp == 91); // [
115			#output->insert(json_consume_array(#ibytes));
116		Else(#temp == 123); // {
117			#output->insert(json_consume_object(#ibytes));
118		Else;
119			#output->insert(json_consume_token(#ibytes, #temp));
120			(#temp == 93) ? Loop_Abort;
121		/If;
122	/While;
123	Return(#output);
124}
125
126// Bil Corry fix
127define json_consume_object(ibytes::bytes)::map => {
128	Local('output' = map,
129		'delimit' = array( 9, 10, 13, 32, 44), // \t\r\n ,
130		'temp' = 0,
131		'key' = null,
132		'val' = null);
133	While((#temp := #ibytes->export8bits) != 125); // }
134		If(#delimit >> #temp);
135			// Discard whitespace
136		Else((#key !== null) && (#temp == 34)); // "
137			#output->insert(#key = json_consume_string(#ibytes));
138			#key = null;
139		Else((#key !== null) && (#temp == 91)); // [
140			#output->insert(#key = json_consume_array(#ibytes));
141			#key = null;
142		Else((#key !== null) && (#temp == 123)); // {
143			#output->insert(#key = json_consume_object(#ibytes));
144			#key = null;
145		Else((#key !== null));
146			#output->insert(#key = json_consume_token(#ibytes, #temp));
147			#key = null;
148		Else;
149			#key = json_consume_string(#ibytes);
150			while(#delimit >> (#temp := #ibytes->export8bits));
151			/while;
152			#temp != 58 ? Loop_Abort;
153		/If;
154	/While;
155
156	If((#output >> '__jsonclass__') && (#output->Find('__jsonclass__')->isa('array')) && (#output->Find('__jsonclass__')->size >= 2) && (#output->Find('__jsonclass__')->First == 'deserialize'));
157		Return(#output->find('__jsonclass__')->Second->First);
158	Else((#output >> 'native') && (#output >> 'comment') && (#output->find('comment') == 'http://www.lassosoft.com/json'));
159		Return(#output->find('native'));
160	/If;
161	Return(#output);
162}
163
164// Bil Corry fix + Ke fix
165define json_deserialize(ibytes::bytes)::any => {
166	#ibytes->removeLeading(bom_utf8);
167
168//============================================================================
169//	Reset marker on provided bytes
170	#ibytes->marker = 0
171//............................................................................
172
173	Local(temp) = #ibytes->export8bits;
174	If(#temp == 91); // [
175		Return(json_consume_array(#ibytes));
176	Else(#temp == 123); // {
177		Return(json_consume_object(#ibytes));
178	else(#temp == 34) // "
179		return json_consume_string(#ibytes)
180	/If;
181}
182
183define json_deserialize(s::string) => json_deserialize(bytes(#s))
184
185/**! json_literal - This is a subclass of String used for JSON encoding.
186
187	A json_literal works exactly like a string, but will be inserted directly
188	rather than being encoded into JSON. This allows JavaScript elements
189	like functions to be inserted into JSON objects. This is most useful
190	when the JSON object will be used within a JavaScript on the local page.
191	[Map: 'fn'=Literal('function(){ ...})] => {'fn': function(){ ...}}
192**/
193define json_literal => type {
194	parent string
195}
196
197/**! json_object - This is a subclass of Map used for JSON encoding.
198
199	An object works exactly like a map, but when it is encoded into JSON all
200	of the keys will be inserted literally. This makes it easy to create a
201	JavaScript object without extraneous quote marks.
202	Object('name'='value') => {name: "value"}
203**/
204define json_object => type {
205	parent map
206	public onCreate(...) => ..onCreate(:#rest or (:))
207}
208
209define json_rpccall(method::string, params=map, id='', host='') => {
210	#id == '' ? #host = Lasso_UniqueID;
211	#host == '' ? #host = 'http://localhost/lassoapps.8/rpc/rpc.lasso';
212	Return(Decode_JSON(Include_URL(#host, -PostParams=Encode_JSON(Map('method' = #method, 'params' = #params, 'id' = #id)))));
213}
214