Skip to content

Commit 5d61fd1

Browse files
committed
#73 support escapeUnicode false
1 parent da3755a commit 5d61fd1

5 files changed

Lines changed: 137 additions & 52 deletions

File tree

src/main/java/com/jsoniter/output/JsonStream.java

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ public void flush() throws IOException {
110110

111111
@Override
112112
public void close() throws IOException {
113+
if (out == null) {
114+
return;
115+
}
113116
if (count > 0) {
114117
flushBuffer();
115118
}
@@ -119,6 +122,9 @@ public void close() throws IOException {
119122
}
120123

121124
final void flushBuffer() throws IOException {
125+
if (out == null) {
126+
return;
127+
}
122128
out.write(buf, 0, count);
123129
count = 0;
124130
}
@@ -457,26 +463,19 @@ public static String serialize(TypeLiteral typeLiteral, Object obj) {
457463
}
458464

459465
public static String serialize(boolean escapeUnicode, Type type, Object obj) {
460-
if (escapeUnicode) {
461-
AsciiOutputStream asciiOutputStream = JsonStreamPool.borrowAsciiOutputStream();
462-
try {
463-
asciiOutputStream.reset();
464-
serialize(type, obj, asciiOutputStream);
465-
return asciiOutputStream.toString();
466-
} finally {
467-
JsonStreamPool.returnAsciiOutputStream(asciiOutputStream);
468-
}
469-
} else {
470-
ByteArrayOutputStream baos = JsonStreamPool.borrowByteArrayOutputStream();
471-
try {
472-
baos.reset();
473-
serialize(type, obj, baos);
474-
return baos.toString("UTF8");
475-
} catch (UnsupportedEncodingException e) {
476-
throw new JsonException(e);
477-
} finally {
478-
JsonStreamPool.returnByteArrayOutputStream(baos);
466+
JsonStream stream = JsonStreamPool.borrowJsonStream();
467+
try {
468+
stream.reset(null);
469+
stream.writeVal(type, obj);
470+
if (escapeUnicode) {
471+
return new String(stream.buf, 0, stream.count);
472+
} else {
473+
return new String(stream.buf, 0, stream.count, "UTF8");
479474
}
475+
} catch (IOException e) {
476+
throw new JsonException(e);
477+
} finally {
478+
JsonStreamPool.returnJsonStream(stream);
480479
}
481480
}
482481

@@ -496,4 +495,5 @@ public static void setIndentionStep(int indentionStep) {
496495
public static void registerNativeEncoder(Class clazz, Encoder.ReflectionEncoder encoder) {
497496
CodegenImplNative.NATIVE_ENCODERS.put(clazz, encoder);
498497
}
498+
499499
}

src/main/java/com/jsoniter/output/StreamImplString.java

Lines changed: 96 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3131
*/
3232
package com.jsoniter.output;
3333

34+
import com.jsoniter.spi.JsonException;
35+
3436
import java.io.IOException;
3537

3638
class StreamImplString {
@@ -39,6 +41,10 @@ class StreamImplString {
3941
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
4042
'a', 'b', 'c', 'd', 'e', 'f'};
4143
private static final boolean[] CAN_DIRECT_WRITE = new boolean[128];
44+
private final static int SURR1_FIRST = 0xD800;
45+
private final static int SURR1_LAST = 0xDBFF;
46+
private final static int SURR2_FIRST = 0xDC00;
47+
private final static int SURR2_LAST = 0xDFFF;
4248

4349
static {
4450
for (int i = 0; i < CAN_DIRECT_WRITE.length; i++) {
@@ -122,41 +128,102 @@ public static final void writeStringWithoutQuote(final JsonStream stream, final
122128
}
123129

124130
private static void writeStringSlowPath(JsonStream stream, String val, int i, int valLen) throws IOException {
131+
boolean escapeUnicode = stream.currentConfig().escapeUnicode();
132+
if (escapeUnicode) {
133+
for (; i < valLen; i++) {
134+
int c = val.charAt(i);
135+
if (c > 125) {
136+
byte b4 = (byte) (c & 0xf);
137+
byte b3 = (byte) (c >> 4 & 0xf);
138+
byte b2 = (byte) (c >> 8 & 0xf);
139+
byte b1 = (byte) (c >> 12 & 0xf);
140+
stream.write((byte) '\\', (byte) 'u', ITOA[b1], ITOA[b2], ITOA[b3], ITOA[b4]);
141+
} else {
142+
writeAsciiChar(stream, c);
143+
}
144+
}
145+
} else {
146+
writeStringSlowPathWithoutEscapeUnicode(stream, val, i, valLen);
147+
}
148+
}
149+
150+
private static void writeStringSlowPathWithoutEscapeUnicode(JsonStream stream, String val, int i, int valLen) throws IOException {
151+
int _surrogate;
125152
for (; i < valLen; i++) {
126153
int c = val.charAt(i);
127154
if (c > 125) {
128-
byte b4 = (byte) (c & 0xf);
129-
byte b3 = (byte) (c >> 4 & 0xf);
130-
byte b2 = (byte) (c >> 8 & 0xf);
131-
byte b1 = (byte) (c >> 12 & 0xf);
132-
stream.write((byte) '\\', (byte) 'u', ITOA[b1], ITOA[b2], ITOA[b3], ITOA[b4]);
133-
} else {
134-
switch (c) {
135-
case '"':
136-
stream.write((byte) '\\', (byte) '"');
137-
break;
138-
case '\\':
139-
stream.write((byte) '\\', (byte) '\\');
140-
break;
141-
case '\b':
142-
stream.write((byte) '\\', (byte) 'b');
143-
break;
144-
case '\f':
145-
stream.write((byte) '\\', (byte) 'f');
146-
break;
147-
case '\n':
148-
stream.write((byte) '\\', (byte) 'n');
149-
break;
150-
case '\r':
151-
stream.write((byte) '\\', (byte) 'r');
155+
if (c < 0x800) { // 2-byte
156+
stream.write(
157+
(byte) (0xc0 | (c >> 6)),
158+
(byte) (0x80 | (c & 0x3f))
159+
);
160+
} else { // 3 or 4 bytes
161+
// Surrogates?
162+
if (c < SURR1_FIRST || c > SURR2_LAST) {
163+
stream.write(
164+
(byte) (0xe0 | (c >> 12)),
165+
(byte) (0x80 | ((c >> 6) & 0x3f)),
166+
(byte) (0x80 | (c & 0x3f))
167+
);
168+
continue;
169+
}
170+
// Yup, a surrogate:
171+
if (c > SURR1_LAST) { // must be from first range
172+
throw new JsonException("illegalSurrogate");
173+
}
174+
_surrogate = c;
175+
// and if so, followed by another from next range
176+
if (i >= valLen) { // unless we hit the end?
152177
break;
153-
case '\t':
154-
stream.write((byte) '\\', (byte) 't');
155-
break;
156-
default:
157-
stream.write(c);
178+
}
179+
int firstPart = _surrogate;
180+
_surrogate = 0;
181+
// Ok, then, is the second part valid?
182+
if (c < SURR2_FIRST || c > SURR2_LAST) {
183+
throw new JsonException("Broken surrogate pair: first char 0x" + Integer.toHexString(firstPart) + ", second 0x" + Integer.toHexString(c) + "; illegal combination");
184+
}
185+
c = 0x10000 + ((firstPart - SURR1_FIRST) << 10) + (c - SURR2_FIRST);
186+
if (c > 0x10FFFF) { // illegal in JSON as well as in XML
187+
throw new JsonException("illegalSurrogate");
188+
}
189+
stream.write(
190+
(byte) (0xf0 | (c >> 18)),
191+
(byte) (0x80 | ((c >> 12) & 0x3f)),
192+
(byte) (0x80 | ((c >> 6) & 0x3f)),
193+
(byte) (0x80 | (c & 0x3f))
194+
);
158195
}
196+
} else {
197+
writeAsciiChar(stream, c);
159198
}
160199
}
161200
}
201+
202+
private static void writeAsciiChar(JsonStream stream, int c) throws IOException {
203+
switch (c) {
204+
case '"':
205+
stream.write((byte) '\\', (byte) '"');
206+
break;
207+
case '\\':
208+
stream.write((byte) '\\', (byte) '\\');
209+
break;
210+
case '\b':
211+
stream.write((byte) '\\', (byte) 'b');
212+
break;
213+
case '\f':
214+
stream.write((byte) '\\', (byte) 'f');
215+
break;
216+
case '\n':
217+
stream.write((byte) '\\', (byte) 'n');
218+
break;
219+
case '\r':
220+
stream.write((byte) '\\', (byte) 'r');
221+
break;
222+
case '\t':
223+
stream.write((byte) '\\', (byte) 't');
224+
break;
225+
default:
226+
stream.write(c);
227+
}
228+
}
162229
}

src/test/java/com/jsoniter/TestGson.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,15 @@ public void test_Expose() {
3737
// test if the iterator reuse will keep right config cache
3838
JsonIterator.deserialize(new GsonCompatibilityMode.Builder().build(),
3939
"{\"field-1\":\"hello\"}", TestObject2.class);
40-
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
40+
Gson gson = new GsonBuilder()
41+
.excludeFieldsWithoutExposeAnnotation()
42+
.create();
4143
TestObject2 obj = gson.fromJson("{\"field1\":\"hello\"}", TestObject2.class);
4244
assertNull(obj.field1);
43-
obj = JsonIterator.deserialize(new GsonCompatibilityMode.Builder()
44-
.excludeFieldsWithoutExposeAnnotation().build(),
45+
GsonCompatibilityMode config = new GsonCompatibilityMode.Builder()
46+
.excludeFieldsWithoutExposeAnnotation()
47+
.build();
48+
obj = JsonIterator.deserialize(config,
4549
"{\"field1\":\"hello\"}", TestObject2.class);
4650
assertNull(obj.field1);
4751
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.jsoniter.output;
2+
3+
import com.jsoniter.spi.Config;
4+
import junit.framework.TestCase;
5+
6+
public class TestString extends TestCase {
7+
public void test_unicode() {
8+
String output = JsonStream.serialize(new Config.Builder().escapeUnicode(false).build(), "中文");
9+
assertEquals("\"中文\"", output);
10+
}
11+
}

src/test/java/com/jsoniter/suite/AllTestCases.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.jsoniter.TestGson;
66
import com.jsoniter.TestNested;
77
import com.jsoniter.TestObject;
8+
import com.jsoniter.TestString;
89
import com.jsoniter.any.TestList;
910
import com.jsoniter.output.*;
1011
import org.junit.runner.RunWith;
@@ -27,7 +28,9 @@
2728
TestObject.class,
2829
com.jsoniter.output.TestObject.class,
2930
TestReadAny.class, TestSkip.class, TestSlice.class,
30-
TestString.class, TestWhatIsNext.class,
31+
TestString.class,
32+
com.jsoniter.output.TestString.class,
33+
TestWhatIsNext.class,
3134
TestAny.class,
3235
com.jsoniter.output.TestArray.class,
3336
com.jsoniter.any.TestArray.class,

0 commit comments

Comments
 (0)