Skip to content

Commit 52cc6a7

Browse files
author
Dmitry Dutikov
committed
Unicode escape chars parsing fixed
1 parent 079415f commit 52cc6a7

File tree

7 files changed

+100
-37
lines changed

7 files changed

+100
-37
lines changed

README.md

+12-2
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,24 @@ import axios from 'axios';
8888
import { Json22RequestInterceptor } from 'json22-axios';
8989

9090
axios.interceptors.request.use(Json22RequestInterceptor());
91-
// interceptor allow you to send and receive JSON22
91+
92+
async function geServerDate() {
93+
try {
94+
const resp = await axios.get('/date');
95+
return resp.data.date;
96+
} catch (e) {
97+
console.error(e);
98+
}
99+
return null;
100+
}
92101
```
93102

94103
## API
95104
Note: JSON22 cannot be used as drop in JSON object replacement due to `parse` and `stringify` methods
96105
arguments incompatibility. But you may not be worried in case you are using first arguments only.
97106
```typescript
98107
class JSON22 {
108+
static readonly mimeType: string;
99109
static parse<T>(text: string, options?: Json22ParseOptions): T;
100110
static stringify(value: any, options?: Json22StringifyOptions): string;
101111
}
@@ -150,7 +160,7 @@ This is the most significant addition. It's allow you to serialize and deseriali
150160
Out of the box it works well with date values.
151161
```javascript
152162
const date = new Date('2022-01-07');
153-
JSON.stringify(date); // => '"2022-01-07T00:00:00.000Z"'
163+
JSON.stringify(date); // => "2022-01-07T00:00:00.000Z"
154164
JSON22.stringify(date); // => Date(1641513600000)
155165
```
156166
```javascript

index.cjs

+29-17
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const State = {
7373
objectMemberValue: 'objectMemberValue',
7474
string: 'string',
7575
escape: 'escape',
76+
escapeUnicode: 'escapeUnicode',
7677
array: 'array',
7778
arrayItem: 'arrayItem',
7879
literal: 'literal',
@@ -192,7 +193,7 @@ class JSON22 {
192193
// ignore
193194
break;
194195
case QUOTATION_MARK:
195-
valueStack.push([]);
196+
valueStack.push([{ start: i, end: i, type: 'string' }]);
196197
stateStack.push(State.string);
197198
break;
198199
case BEGIN_OBJECT:
@@ -250,44 +251,55 @@ class JSON22 {
250251
case (code < 0x20 ? c : undefined):
251252
throw new Error(`Character ${c} at position ${i} is not allowed, try to escape it`);
252253
case ESCAPE_CHAR:
254+
const region = valueStack.top.pop();
255+
valueStack.top.push(text.slice(region.start+1, region.end+1));
253256
stateStack.push(State.escape);
254257
break;
255258
case QUOTATION_MARK:
256-
const chars = valueStack.pop();
257-
const value = chars.join('');
259+
const r = valueStack.top.pop();
260+
valueStack.top.push(text.slice(r.start+1, r.end+1));
261+
const substrings = valueStack.pop();
262+
const value = substrings.join('');
258263
valueStack.push(value);
259264
stateStack.pop(State.string);
260265
stateStack.pop(); // out of value or objectMemberKey
261266
break;
262267
default:
263-
valueStack.top.push(c);
268+
valueStack.top[valueStack.top.length-1].end = i;
264269
break;
265270
}
266271
break;
267272
case State.escape:
268273
switch (c) {
269274
case (ESCAPE_SHORTENS.indexOf(c) > -1 ? c : undefined):
270275
valueStack.top.push(ESCAPE_MAP[c]);
276+
valueStack.top.push({ start: i, end: i, type: 'string' });
271277
stateStack.pop();
272278
break;
273279
case 'u': // unicode escape like \u0020
274-
valueStack.push(['unicode']);
280+
valueStack.top.push({ start: i, end: i, type: 'escapeUnicode' });
281+
stateStack.switch(State.escape, State.escapeUnicode);
275282
break;
283+
default:
284+
throw new Error(`Unexpected character ${c} at ${i}, escape sequence expected`);
285+
}
286+
break;
287+
case State.escapeUnicode:
288+
switch(c) {
276289
case (HEXADECIMALS.indexOf(c) > -1 ? c : undefined):
277-
if (valueStack.top[0] === 'unicode') {
278-
if (valueStack.top.length === 5) {
279-
const top = valueStack.pop();
280-
top.shift();
281-
const code = parseInt(top.join(''), 16);
282-
valueStack.top.push(String.fromCharCode(code));
283-
stateStack.pop();
284-
}
285-
} else {
286-
throw new Error(`Unexpected character ${c} at ${i}, unicode escape sequence expected`);
290+
const region = valueStack.top[valueStack.top.length-1];
291+
region.end = i;
292+
if (region.end - region.start === 4) {
293+
valueStack.top.pop();
294+
const codeText = text.slice(region.start+1, region.end+1);
295+
const code = parseInt(codeText, 16);
296+
valueStack.top.push(String.fromCharCode(code));
297+
valueStack.top.push({ start: i, end: i, type: 'string' });
298+
stateStack.pop(State.escapeUnicode);
287299
}
288300
break;
289301
default:
290-
throw new Error(`Unexpected character ${c} at ${i}, escape sequence expected`);
302+
throw new Error(`Unexpected character ${c} at ${i}, unicode escape sequence expected`);
291303
}
292304
break;
293305
case State.object:
@@ -309,7 +321,7 @@ class JSON22 {
309321
// ignore
310322
break;
311323
case QUOTATION_MARK:
312-
valueStack.push([]);
324+
valueStack.push([{ start: i, end: i, type: 'string' }]);
313325
stateStack.push(State.string);
314326
break;
315327
case END_OBJECT:

index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export declare class JSON22 {
2+
static readonly mimeType: string;
23
static parse<T>(text: string, options?: Json22ParseOptions): T;
34
static stringify(value: any, options?: Json22StringifyOptions): string;
45
}

index.js

+29-17
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const State = {
7373
objectMemberValue: 'objectMemberValue',
7474
string: 'string',
7575
escape: 'escape',
76+
escapeUnicode: 'escapeUnicode',
7677
array: 'array',
7778
arrayItem: 'arrayItem',
7879
literal: 'literal',
@@ -192,7 +193,7 @@ export class JSON22 {
192193
// ignore
193194
break;
194195
case QUOTATION_MARK:
195-
valueStack.push([]);
196+
valueStack.push([{ start: i, end: i, type: 'string' }]);
196197
stateStack.push(State.string);
197198
break;
198199
case BEGIN_OBJECT:
@@ -250,44 +251,55 @@ export class JSON22 {
250251
case (code < 0x20 ? c : undefined):
251252
throw new Error(`Character ${c} at position ${i} is not allowed, try to escape it`);
252253
case ESCAPE_CHAR:
254+
const region = valueStack.top.pop();
255+
valueStack.top.push(text.slice(region.start+1, region.end+1));
253256
stateStack.push(State.escape);
254257
break;
255258
case QUOTATION_MARK:
256-
const chars = valueStack.pop();
257-
const value = chars.join('');
259+
const r = valueStack.top.pop();
260+
valueStack.top.push(text.slice(r.start+1, r.end+1));
261+
const substrings = valueStack.pop();
262+
const value = substrings.join('');
258263
valueStack.push(value);
259264
stateStack.pop(State.string);
260265
stateStack.pop(); // out of value or objectMemberKey
261266
break;
262267
default:
263-
valueStack.top.push(c);
268+
valueStack.top[valueStack.top.length-1].end = i;
264269
break;
265270
}
266271
break;
267272
case State.escape:
268273
switch (c) {
269274
case (ESCAPE_SHORTENS.indexOf(c) > -1 ? c : undefined):
270275
valueStack.top.push(ESCAPE_MAP[c]);
276+
valueStack.top.push({ start: i, end: i, type: 'string' });
271277
stateStack.pop();
272278
break;
273279
case 'u': // unicode escape like \u0020
274-
valueStack.push(['unicode']);
280+
valueStack.top.push({ start: i, end: i, type: 'escapeUnicode' });
281+
stateStack.switch(State.escape, State.escapeUnicode);
275282
break;
283+
default:
284+
throw new Error(`Unexpected character ${c} at ${i}, escape sequence expected`);
285+
}
286+
break;
287+
case State.escapeUnicode:
288+
switch(c) {
276289
case (HEXADECIMALS.indexOf(c) > -1 ? c : undefined):
277-
if (valueStack.top[0] === 'unicode') {
278-
if (valueStack.top.length === 5) {
279-
const top = valueStack.pop();
280-
top.shift();
281-
const code = parseInt(top.join(''), 16);
282-
valueStack.top.push(String.fromCharCode(code));
283-
stateStack.pop();
284-
}
285-
} else {
286-
throw new Error(`Unexpected character ${c} at ${i}, unicode escape sequence expected`);
290+
const region = valueStack.top[valueStack.top.length-1];
291+
region.end = i;
292+
if (region.end - region.start === 4) {
293+
valueStack.top.pop();
294+
const codeText = text.slice(region.start+1, region.end+1);
295+
const code = parseInt(codeText, 16);
296+
valueStack.top.push(String.fromCharCode(code));
297+
valueStack.top.push({ start: i, end: i, type: 'string' });
298+
stateStack.pop(State.escapeUnicode);
287299
}
288300
break;
289301
default:
290-
throw new Error(`Unexpected character ${c} at ${i}, escape sequence expected`);
302+
throw new Error(`Unexpected character ${c} at ${i}, unicode escape sequence expected`);
291303
}
292304
break;
293305
case State.object:
@@ -309,7 +321,7 @@ export class JSON22 {
309321
// ignore
310322
break;
311323
case QUOTATION_MARK:
312-
valueStack.push([]);
324+
valueStack.push([{ start: i, end: i, type: 'string' }]);
313325
stateStack.push(State.string);
314326
break;
315327
case END_OBJECT:

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "json22",
3-
"version": "0.0.7",
3+
"version": "0.0.8",
44
"description": "JSON superset with an ability to deal with classes and extended support for number values",
55
"author": {
66
"email": "dancecoder@gmail.com",

test/objects.test.js

+7
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ suite('Object values parsing tests', () => {
4646
assert.deepEqual(parsed, o);
4747
});
4848

49+
test('parsing object with escaped chars in a key', () => {
50+
const o = {"\u0000":1234,"\u0001":"abcd","\u0002":true,"\u0003":null,"last":false};
51+
const json = '{"\\u0000":1234,"\\u0001":"abcd","\\u0002":true,"\\u0003":null,"last":false}';
52+
const parsed = JSON22.parse(json);
53+
assert.deepEqual(parsed, o);
54+
});
55+
4956
test('check for incorrect char after entry value', () => {
5057
assert.throws(() => JSON22.parse('{ "k": "v" Z }'));
5158
});

test/strings.test.js

+21
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,27 @@ suite('String values parsing tests', () => {
1818
assert.equal(parsed, s);
1919
});
2020

21+
test('parsing escape only string', () => {
22+
const s = '\t\t\t\t\t';
23+
const json = '"\\t\\t\\t\\t\\t"';
24+
const parsed = JSON22.parse(json);
25+
assert.equal(parsed, s);
26+
});
27+
28+
test('parsing unicode escape sequences', () => {
29+
const s = 'key:\u0030value';
30+
const json = '"key:\\u0030value"';
31+
const parsed = JSON22.parse(json);
32+
assert.equal(parsed, s);
33+
});
34+
35+
test('parsing unicode escape only string', () => {
36+
const s = '\u0030\u0030\u0030\u0030\u0030';
37+
const json = '"\\u0030\\u0030\\u0030\\u0030\\u0030"';
38+
const parsed = JSON22.parse(json);
39+
assert.equal(parsed, s);
40+
});
41+
2142
test('check for unsupported characters', () => {
2243
// NOTE: characters with code < 0x20 are not allowed (be JSON spec)
2344
const json = '"\u0019 not allowed"';

0 commit comments

Comments
 (0)