diff --git a/pom.xml b/pom.xml index 01bf1721..4840503f 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 com.jsoniter - 0.9.22 + 0.9.24-SNAPSHOT jsoniter json iterator jsoniter (json-iterator) is fast and flexible JSON parser available in Java and Go @@ -46,25 +46,25 @@ org.javassist javassist - 3.21.0-GA + 3.22.0-GA true com.fasterxml.jackson.core jackson-annotations - 2.8.5 + 2.9.5 true com.fasterxml.jackson.core jackson-databind - 2.8.5 + 2.9.5 true com.google.code.gson gson - 2.2.4 + 2.8.3 true @@ -119,7 +119,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.6.0 + 3.7.0 1.6 1.6 @@ -129,7 +129,7 @@ org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.0.1 attach-sources @@ -142,7 +142,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.9.1 + 3.0.0 attach-javadocs @@ -158,7 +158,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.5 + 1.6 sign-artifacts @@ -172,7 +172,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.7 + 1.6.8 true ossrh @@ -194,7 +194,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.19.1 + 2.21.0 methods 1 diff --git a/src/main/java/com/jsoniter/Codegen.java b/src/main/java/com/jsoniter/Codegen.java index ad2013d5..7cf7318d 100644 --- a/src/main/java/com/jsoniter/Codegen.java +++ b/src/main/java/com/jsoniter/Codegen.java @@ -96,16 +96,12 @@ private static void addPlaceholderDecoderToSupportRecursiveStructure(final Strin public Object decode(JsonIterator iter) throws IOException { Decoder decoder = JsoniterSpi.getDecoder(cacheKey); if (this == decoder) { - for(int i = 0; i < 30; i++) { + for(int i = 0; (i < 30) && (this == decoder); i++) { decoder = JsoniterSpi.getDecoder(cacheKey); - if (this == decoder) { - try { - Thread.sleep(1000); + try { + Thread.sleep(1000); } catch (InterruptedException e) { throw new JsonException(e); - } - } else { - break; } } if (this == decoder) { diff --git a/src/main/java/com/jsoniter/IterImpl.java b/src/main/java/com/jsoniter/IterImpl.java index 65563134..ad779fd8 100644 --- a/src/main/java/com/jsoniter/IterImpl.java +++ b/src/main/java/com/jsoniter/IterImpl.java @@ -59,7 +59,7 @@ final static void skipArray(JsonIterator iter) throws IOException { case '[': // If open symbol, increase level level++; break; - case ']': // If close symbol, increase level + case ']': // If close symbol, decrease level level--; // If we have returned to the original level, we're done @@ -85,7 +85,7 @@ final static void skipObject(JsonIterator iter) throws IOException { case '{': // If open symbol, increase level level++; break; - case '}': // If close symbol, increase level + case '}': // If close symbol, decrease level level--; // If we have returned to the original level, we're done @@ -393,10 +393,6 @@ static final int readInt(final JsonIterator iter, final byte c) throws IOExcepti static final long readLong(final JsonIterator iter, final byte c) throws IOException { long ind = IterImplNumber.intDigits[c]; - if (ind == 0) { - IterImplForStreaming.assertNotLeadingZero(iter); - return 0; - } if (ind == IterImplNumber.INVALID_CHAR_FOR_NUMBER) { throw iter.reportError("readLong", "expect 0~9"); } diff --git a/src/main/java/com/jsoniter/IterImplForStreaming.java b/src/main/java/com/jsoniter/IterImplForStreaming.java index 7e29f770..2cef3a16 100644 --- a/src/main/java/com/jsoniter/IterImplForStreaming.java +++ b/src/main/java/com/jsoniter/IterImplForStreaming.java @@ -71,7 +71,7 @@ final static void skipArray(JsonIterator iter) throws IOException { case '[': // If open symbol, increase level level++; break; - case ']': // If close symbol, increase level + case ']': // If close symbol, decrease level level--; // If we have returned to the original level, we're done @@ -101,7 +101,7 @@ final static void skipObject(JsonIterator iter) throws IOException { case '{': // If open symbol, increase level level++; break; - case '}': // If close symbol, increase level + case '}': // If close symbol, decrease level level--; // If we have returned to the original level, we're done @@ -147,7 +147,8 @@ final static void skipString(JsonIterator iter) throws IOException { throw iter.reportError("skipString", "incomplete string"); } if (escaped) { - iter.head = 1; // skip the first char as last char is \ + // TODO add unit test to prove/verify bug + iter.head += 1; // skip the first char as last char is \ } } else { iter.head = end; @@ -274,19 +275,19 @@ public final static boolean loadMore(JsonIterator iter) throws IOException { } private static boolean keepSkippedBytesThenRead(JsonIterator iter) throws IOException { - int n; - int offset; - if (iter.skipStartedAt == 0 || iter.skipStartedAt < iter.tail / 2) { - byte[] newBuf = new byte[iter.buf.length * 2]; - offset = iter.tail - iter.skipStartedAt; - System.arraycopy(iter.buf, iter.skipStartedAt, newBuf, 0, offset); - iter.buf = newBuf; - n = iter.in.read(iter.buf, offset, iter.buf.length - offset); - } else { - offset = iter.tail - iter.skipStartedAt; - System.arraycopy(iter.buf, iter.skipStartedAt, iter.buf, 0, offset); - n = iter.in.read(iter.buf, offset, iter.buf.length - offset); - } + int offset = iter.tail - iter.skipStartedAt; + byte[] srcBuffer = iter.buf; + // Check there is no unused buffer capacity + if ((getUnusedBufferByteCount(iter)) == 0) { + // If auto expand buffer enabled, then create larger buffer + if (iter.autoExpandBufferStep > 0) { + iter.buf = new byte[iter.buf.length + iter.autoExpandBufferStep]; + } else { + throw iter.reportError("loadMore", String.format("buffer is full and autoexpansion is disabled. tail: [%s] skipStartedAt: [%s]", iter.tail, iter.skipStartedAt)); + } + } + System.arraycopy(srcBuffer, iter.skipStartedAt, iter.buf, 0, offset); + int n = iter.in.read(iter.buf, offset, iter.buf.length - offset); iter.skipStartedAt = 0; if (n < 1) { if (n == -1) { @@ -301,6 +302,11 @@ private static boolean keepSkippedBytesThenRead(JsonIterator iter) throws IOExce return true; } + private static int getUnusedBufferByteCount(JsonIterator iter) { + // Get bytes from 0 to skipStart + from tail till end + return iter.buf.length - iter.tail + iter.skipStartedAt; + } + final static byte readByte(JsonIterator iter) throws IOException { if (iter.head == iter.tail) { if (!loadMore(iter)) { @@ -540,6 +546,16 @@ static int readIntSlowPath(final JsonIterator iter, int value) throws IOExceptio public static final double readDoubleSlowPath(final JsonIterator iter) throws IOException { try { numberChars numberChars = readNumber(iter); + if (numberChars.charsLength == 0 && iter.whatIsNext() == ValueType.STRING) { + String possibleInf = iter.readString(); + if ("infinity".equals(possibleInf)) { + return Double.POSITIVE_INFINITY; + } + if ("-infinity".equals(possibleInf)) { + return Double.NEGATIVE_INFINITY; + } + throw iter.reportError("readDoubleSlowPath", "expect number but found string: " + possibleInf); + } return Double.valueOf(new String(numberChars.chars, 0, numberChars.charsLength)); } catch (NumberFormatException e) { throw iter.reportError("readDoubleSlowPath", e.toString()); @@ -633,8 +649,7 @@ static final int readInt(final JsonIterator iter, final byte c) throws IOExcepti static void assertNotLeadingZero(JsonIterator iter) throws IOException { try { - byte nextByte = IterImpl.readByte(iter); - iter.unreadByte(); + byte nextByte = iter.buf[iter.head]; int ind2 = IterImplNumber.intDigits[nextByte]; if (ind2 == IterImplNumber.INVALID_CHAR_FOR_NUMBER) { return; diff --git a/src/main/java/com/jsoniter/IterImplNumber.java b/src/main/java/com/jsoniter/IterImplNumber.java index adc2bf69..c521cdd5 100644 --- a/src/main/java/com/jsoniter/IterImplNumber.java +++ b/src/main/java/com/jsoniter/IterImplNumber.java @@ -91,8 +91,17 @@ public static final int readInt(final JsonIterator iter) throws IOException { public static final long readLong(JsonIterator iter) throws IOException { byte c = IterImpl.nextToken(iter); if (c == '-') { - return IterImpl.readLong(iter, IterImpl.readByte(iter)); + c = IterImpl.readByte(iter); + if (IterImplNumber.intDigits[c] == 0) { + IterImplForStreaming.assertNotLeadingZero(iter); + return 0; + } + return IterImpl.readLong(iter, c); } else { + if (IterImplNumber.intDigits[c] == 0) { + IterImplForStreaming.assertNotLeadingZero(iter); + return 0; + } long val = IterImpl.readLong(iter, c); if (val == Long.MIN_VALUE) { throw iter.reportError("readLong", "value is too large for long"); diff --git a/src/main/java/com/jsoniter/JsonIterator.java b/src/main/java/com/jsoniter/JsonIterator.java index 33f224d3..c198540b 100644 --- a/src/main/java/com/jsoniter/JsonIterator.java +++ b/src/main/java/com/jsoniter/JsonIterator.java @@ -21,6 +21,9 @@ public class JsonIterator implements Closeable { final static ValueType[] valueTypes = new ValueType[256]; InputStream in; byte[] buf; + // Whenever buf is not large enough new one is created with size of + // buf.length + autoExpandBufferStep. Set to < 1 to disable auto expanding. + int autoExpandBufferStep; int head; int tail; int skipStartedAt = -1; // skip should keep bytes starting at this pos @@ -60,13 +63,22 @@ private JsonIterator(InputStream in, byte[] buf, int head, int tail) { this.tail = tail; } + private JsonIterator(InputStream in, byte[] buf, int autoExpandBufferStep) { + this(in, buf, 0, 0); + this.autoExpandBufferStep = autoExpandBufferStep; + } + public JsonIterator() { this(null, new byte[0], 0, 0); } public static JsonIterator parse(InputStream in, int bufSize) { + return parse(in, bufSize, bufSize); + } + + public static JsonIterator parse(InputStream in, int bufSize, int autoExpandBufferStep) { enableStreamingSupport(); - return new JsonIterator(in, new byte[bufSize], 0, 0); + return new JsonIterator(in, new byte[bufSize], autoExpandBufferStep); } public static JsonIterator parse(byte[] buf) { @@ -292,13 +304,14 @@ public final Object read() throws IOException { return readString(); case NUMBER: IterImplForStreaming.numberChars numberChars = IterImplForStreaming.readNumber(this); - Double number = Double.valueOf(new String(numberChars.chars, 0, numberChars.charsLength)); + String numberStr = new String(numberChars.chars, 0, numberChars.charsLength); + Double number = Double.valueOf(numberStr); if (numberChars.dotFound) { return number; } double doubleNumber = number; if (doubleNumber == Math.floor(doubleNumber) && !Double.isInfinite(doubleNumber)) { - long longNumber = (long) doubleNumber; + long longNumber = Long.valueOf(numberStr); if (longNumber <= Integer.MAX_VALUE && longNumber >= Integer.MIN_VALUE) { return (int) longNumber; } diff --git a/src/main/java/com/jsoniter/JsonIteratorPool.java b/src/main/java/com/jsoniter/JsonIteratorPool.java index 00f88e63..f0324d02 100644 --- a/src/main/java/com/jsoniter/JsonIteratorPool.java +++ b/src/main/java/com/jsoniter/JsonIteratorPool.java @@ -22,6 +22,7 @@ public static JsonIterator borrowJsonIterator() { public static void returnJsonIterator(JsonIterator iter) { iter.configCache = null; + iter.existingObject = null; if (slot1.get() == null) { slot1.set(iter); return; diff --git a/src/main/java/com/jsoniter/MapKeyDecoders.java b/src/main/java/com/jsoniter/MapKeyDecoders.java index 4fa3d0cf..0bb75a30 100644 --- a/src/main/java/com/jsoniter/MapKeyDecoders.java +++ b/src/main/java/com/jsoniter/MapKeyDecoders.java @@ -22,11 +22,14 @@ private static Decoder createMapKeyDecoder(Type mapKeyType) { if (String.class == mapKeyType) { return new StringKeyDecoder(); } + if (mapKeyType instanceof Class && ((Class) mapKeyType).isEnum()) { + return new EnumKeyDecoder((Class) mapKeyType); + } Decoder decoder = CodegenImplNative.NATIVE_DECODERS.get(mapKeyType); if (decoder != null) { return new NumberKeyDecoder(decoder); } - throw new JsonException("can not encode map key type: " + mapKeyType); + throw new JsonException("can not decode map key type: " + mapKeyType); } private static class StringKeyDecoder implements Decoder { @@ -37,6 +40,20 @@ public Object decode(JsonIterator iter) throws IOException { } } + private static class EnumKeyDecoder implements Decoder { + + private final Class enumClass; + + private EnumKeyDecoder(Class enumClass) { + this.enumClass = enumClass; + } + + @Override + public Object decode(JsonIterator iter) throws IOException { + return iter.read(enumClass); + } + } + private static class NumberKeyDecoder implements Decoder { private final Decoder decoder; diff --git a/src/main/java/com/jsoniter/any/ArrayLazyAny.java b/src/main/java/com/jsoniter/any/ArrayLazyAny.java index 2205f71c..13983641 100644 --- a/src/main/java/com/jsoniter/any/ArrayLazyAny.java +++ b/src/main/java/com/jsoniter/any/ArrayLazyAny.java @@ -206,7 +206,11 @@ private class LazyIterator implements Iterator { public LazyIterator() { index = 0; - next = fillCacheUntil(index); + try { + next = fillCacheUntil(index); + } catch (IndexOutOfBoundsException e) { + next = null; + } } @Override diff --git a/src/main/java/com/jsoniter/any/LazyAny.java b/src/main/java/com/jsoniter/any/LazyAny.java index 246fca76..d088241e 100644 --- a/src/main/java/com/jsoniter/any/LazyAny.java +++ b/src/main/java/com/jsoniter/any/LazyAny.java @@ -68,7 +68,7 @@ public final T as(TypeLiteral typeLiteral) { } public String toString() { - return new String(data, head, tail - head); + return new String(data, head, tail - head).trim(); } protected final JsonIterator parse() { diff --git a/src/main/java/com/jsoniter/extra/Base64Support.java b/src/main/java/com/jsoniter/extra/Base64Support.java index 16c60dfb..676178f7 100644 --- a/src/main/java/com/jsoniter/extra/Base64Support.java +++ b/src/main/java/com/jsoniter/extra/Base64Support.java @@ -11,7 +11,7 @@ import java.io.IOException; /** - * byte[] <=> base64 + * byte[] <=> base64 */ public class Base64Support { private static boolean enabled; diff --git a/src/main/java/com/jsoniter/output/CodegenImplMap.java b/src/main/java/com/jsoniter/output/CodegenImplMap.java index 52250efd..006817d3 100644 --- a/src/main/java/com/jsoniter/output/CodegenImplMap.java +++ b/src/main/java/com/jsoniter/output/CodegenImplMap.java @@ -51,11 +51,6 @@ public static CodegenResult genMap(String cacheKey, ClassInfo classInfo) { ctx.append("stream.writeMore();"); } genWriteMapKey(ctx, keyType, noIndention); - if (noIndention) { - ctx.append("stream.write(':');"); - } else { - ctx.append("stream.write((byte)':', (byte)' ');"); - } if (isCollectionValueNullable) { ctx.append("if (entry.getValue() == null) { stream.writeNull(); } else {"); CodegenImplNative.genWriteOp(ctx, "entry.getValue()", valueType, true); diff --git a/src/main/java/com/jsoniter/output/JsonStream.java b/src/main/java/com/jsoniter/output/JsonStream.java index 88f77077..7886bc05 100644 --- a/src/main/java/com/jsoniter/output/JsonStream.java +++ b/src/main/java/com/jsoniter/output/JsonStream.java @@ -442,52 +442,59 @@ public static void serialize(TypeLiteral typeLiteral, Object obj, OutputStream o } public static void serialize(Type type, Object obj, OutputStream out) { - JsonStream stream = JsonStreamPool.borrowJsonStream(); - try { - try { - stream.reset(out); - stream.writeVal(type, obj); - } finally { - stream.close(); - } - } catch (IOException e) { - throw new JsonException(e); - } finally { - JsonStreamPool.returnJsonStream(stream); - } + serialize(type, obj, out, false); } public static String serialize(Config config, Object obj) { - JsoniterSpi.setCurrentConfig(config); - try { - return serialize(config.escapeUnicode(), obj.getClass(), obj); - } finally { - JsoniterSpi.clearCurrentConfig(); - } + return serialize(config, obj.getClass(), obj); } public static String serialize(Object obj) { - return serialize(JsoniterSpi.getCurrentConfig().escapeUnicode(), obj.getClass(), obj); + return serialize(obj.getClass(), obj); } public static String serialize(Config config, TypeLiteral typeLiteral, Object obj) { + return serialize(config, typeLiteral.getType(), obj); + } + + private static String serialize(Config config, Type type, Object obj) { + final Config configBackup = JsoniterSpi.getCurrentConfig(); + // Set temporary config JsoniterSpi.setCurrentConfig(config); try { - return serialize(config.escapeUnicode(), typeLiteral.getType(), obj); + return serialize(type, obj); } finally { - JsoniterSpi.clearCurrentConfig(); + // Revert old config + JsoniterSpi.setCurrentConfig(configBackup); } } public static String serialize(TypeLiteral typeLiteral, Object obj) { - return serialize(JsoniterSpi.getCurrentConfig().escapeUnicode(), typeLiteral.getType(), obj); + return serialize(typeLiteral.getType(), obj); } public static String serialize(boolean escapeUnicode, Type type, Object obj) { - JsonStream stream = JsonStreamPool.borrowJsonStream(); + final Config currentConfig = JsoniterSpi.getCurrentConfig(); + return serialize(currentConfig.copyBuilder().escapeUnicode(escapeUnicode).build(), type, obj); + } + + private static String serialize(Type type, Object obj) { + return serialize(type, obj, null, true); + } + + private static String serialize(Type type, Object obj, OutputStream out, boolean returnObjAsString) { + final JsonStream stream = JsonStreamPool.borrowJsonStream(); + final boolean escapeUnicode = JsoniterSpi.getCurrentConfig().escapeUnicode(); try { - stream.reset(null); - stream.writeVal(type, obj); + try { + stream.reset(out); + stream.writeVal(type, obj); + } finally { + stream.close(); + } + if (!returnObjAsString) { + return ""; + } if (escapeUnicode) { return new String(stream.buf, 0, stream.count); } else { diff --git a/src/main/java/com/jsoniter/output/MapKeyEncoders.java b/src/main/java/com/jsoniter/output/MapKeyEncoders.java index 443aa291..401ebfbe 100644 --- a/src/main/java/com/jsoniter/output/MapKeyEncoders.java +++ b/src/main/java/com/jsoniter/output/MapKeyEncoders.java @@ -29,6 +29,9 @@ private static Encoder createDefaultEncoder(Type mapKeyType) { if (mapKeyType instanceof WildcardType) { return new DynamicKeyEncoder(); } + if (mapKeyType instanceof Class && ((Class) mapKeyType).isEnum()) { + return new StringKeyEncoder(); + } Encoder.ReflectionEncoder encoder = CodegenImplNative.NATIVE_ENCODERS.get(mapKeyType); if (encoder != null) { return new NumberKeyEncoder(encoder); diff --git a/src/main/java/com/jsoniter/output/StreamImplNumber.java b/src/main/java/com/jsoniter/output/StreamImplNumber.java index cf6ef68e..54e039c6 100644 --- a/src/main/java/com/jsoniter/output/StreamImplNumber.java +++ b/src/main/java/com/jsoniter/output/StreamImplNumber.java @@ -212,10 +212,18 @@ public static final void writeLong(final JsonStream stream, long value) throws I public static final void writeFloat(JsonStream stream, float val) throws IOException { if (val < 0) { + if (val == Float.NEGATIVE_INFINITY) { + stream.writeVal("-Infinity"); + return; + } stream.write('-'); val = -val; } if (val > 0x4ffffff) { + if (val == Float.POSITIVE_INFINITY) { + stream.writeVal("Infinity"); + return; + } stream.writeRaw(Float.toString(val)); return; } @@ -240,10 +248,18 @@ public static final void writeFloat(JsonStream stream, float val) throws IOExcep public static final void writeDouble(JsonStream stream, double val) throws IOException { if (val < 0) { + if (val == Double.NEGATIVE_INFINITY) { + stream.writeVal("-Infinity"); + return; + } val = -val; stream.write('-'); } if (val > 0x4ffffff) { + if (val == Double.POSITIVE_INFINITY) { + stream.writeVal("Infinity"); + return; + } stream.writeRaw(Double.toString(val)); return; } diff --git a/src/main/java/com/jsoniter/output/StreamImplString.java b/src/main/java/com/jsoniter/output/StreamImplString.java index 7116f359..7c4a27a7 100644 --- a/src/main/java/com/jsoniter/output/StreamImplString.java +++ b/src/main/java/com/jsoniter/output/StreamImplString.java @@ -48,7 +48,7 @@ class StreamImplString { static { for (int i = 0; i < CAN_DIRECT_WRITE.length; i++) { - if (i > 31 && i < 126 && i != '"' && i != '\\') { + if (i > 31 && i <= 126 && i != '"' && i != '\\') { CAN_DIRECT_WRITE[i] = true; } } @@ -132,7 +132,7 @@ private static void writeStringSlowPath(JsonStream stream, String val, int i, in if (escapeUnicode) { for (; i < valLen; i++) { int c = val.charAt(i); - if (c > 125) { + if (c > 127) { writeAsSlashU(stream, c); } else { writeAsciiChar(stream, c); @@ -147,7 +147,7 @@ private static void writeStringSlowPathWithoutEscapeUnicode(JsonStream stream, S int _surrogate; for (; i < valLen; i++) { int c = val.charAt(i); - if (c > 125) { + if (c > 127) { if (c < 0x800) { // 2-byte stream.write( (byte) (0xc0 | (c >> 6)), diff --git a/src/main/java/com/jsoniter/spi/JsoniterSpi.java b/src/main/java/com/jsoniter/spi/JsoniterSpi.java index 7f505e1a..4b40e77e 100644 --- a/src/main/java/com/jsoniter/spi/JsoniterSpi.java +++ b/src/main/java/com/jsoniter/spi/JsoniterSpi.java @@ -43,6 +43,7 @@ public static void setCurrentConfig(Config val) { currentConfig.set(val); } + // TODO usage of this method leads to potentially unexpected side effects. All usage should be checked. public static void clearCurrentConfig() { currentConfig.set(defaultConfig); } diff --git a/src/test/java/com/jsoniter/IterImplForStreamingTest.java b/src/test/java/com/jsoniter/IterImplForStreamingTest.java index c190d469..e0432d39 100644 --- a/src/test/java/com/jsoniter/IterImplForStreamingTest.java +++ b/src/test/java/com/jsoniter/IterImplForStreamingTest.java @@ -1,6 +1,13 @@ package com.jsoniter; +import com.jsoniter.any.Any; +import com.jsoniter.spi.JsonException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import junit.framework.TestCase; +import org.junit.experimental.categories.Category; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; public class IterImplForStreamingTest extends TestCase { @@ -11,4 +18,76 @@ public void testReadMaxDouble() throws Exception { String number = new String(numberChars.chars, 0, numberChars.charsLength); assertEquals(maxDouble, number); } -} \ No newline at end of file + + @Category(StreamingCategory.class) + public void testLoadMore() throws IOException { + final String originalContent = "1234567890"; + final byte[] src = ("{\"a\":\"" + originalContent + "\"}").getBytes(); + + int initialBufferSize; + Any parsedString; + // Case #1: Data fits into initial buffer, autoresizing on + // Input must definitely fit into such large buffer + initialBufferSize = src.length * 2; + JsonIterator jsonIterator = JsonIterator.parse(getSluggishInputStream(src), initialBufferSize, 512); + jsonIterator.readObject(); + parsedString = jsonIterator.readAny(); + assertEquals(originalContent, parsedString.toString()); + // Check buffer was not expanded + assertEquals(initialBufferSize, jsonIterator.buf.length); + + // Case #2: Data does not fit into initial buffer, autoresizing off + initialBufferSize = originalContent.length() / 2; + jsonIterator = JsonIterator.parse(getSluggishInputStream(src), initialBufferSize, 0); + jsonIterator.readObject(); + try { + jsonIterator.readAny(); + fail("Expect to fail because buffer is too small."); + } catch (JsonException e) { + if (!e.getMessage().startsWith("loadMore")) { + throw e; + } + } + // Check buffer was not expanded + assertEquals(initialBufferSize, jsonIterator.buf.length); + + // Case #3: Data does fit into initial buffer, autoresizing on + initialBufferSize = originalContent.length() / 2; + int autoExpandBufferStep = initialBufferSize * 3; + jsonIterator = JsonIterator.parse(getSluggishInputStream(src), initialBufferSize, autoExpandBufferStep); + jsonIterator.readObject(); + parsedString = jsonIterator.readAny(); + assertEquals(originalContent, parsedString.toString()); + // Check buffer was expanded exactly once + assertEquals(initialBufferSize + autoExpandBufferStep, jsonIterator.buf.length); + + // Case #4: Data does not fit (but largest string does) into initial buffer, autoresizing on + initialBufferSize = originalContent.length() + 2; + jsonIterator = JsonIterator.parse(new ByteArrayInputStream(src), initialBufferSize, 0); + jsonIterator.readObject(); + parsedString = jsonIterator.readAny(); + assertEquals(originalContent, parsedString.toString()); + // Check buffer was expanded exactly once + assertEquals(initialBufferSize, jsonIterator.buf.length); + } + + private static InputStream getSluggishInputStream(final byte[] src) { + return new InputStream() { + int position = 0; + + @Override + public int read() throws IOException { + throw new NotImplementedException(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (position < src.length) { + b[off] = src[position++]; + return 1; + } + return -1; + } + }; + } +} diff --git a/src/test/java/com/jsoniter/TestFloat.java b/src/test/java/com/jsoniter/TestFloat.java index b1e9d82c..5fc0f851 100644 --- a/src/test/java/com/jsoniter/TestFloat.java +++ b/src/test/java/com/jsoniter/TestFloat.java @@ -1,6 +1,5 @@ package com.jsoniter; -import com.fasterxml.jackson.databind.ObjectMapper; import junit.framework.TestCase; import org.junit.experimental.categories.Category; @@ -90,4 +89,12 @@ public void testChooseDouble() { number = JsonIterator.deserialize("1.0", Object.class); assertEquals(1.0, number); } + + public void testInfinity() { + assertTrue(JsonIterator.deserialize("\"-infinity\"", Double.class) == Double.NEGATIVE_INFINITY); + assertTrue(JsonIterator.deserialize("\"-infinity\"", Float.class) == Float.NEGATIVE_INFINITY); + assertTrue(JsonIterator.deserialize("\"infinity\"", Double.class) == Double.POSITIVE_INFINITY); + assertTrue(JsonIterator.deserialize("\"infinity\"", Float.class) == Float.POSITIVE_INFINITY); + } + } diff --git a/src/test/java/com/jsoniter/TestMap.java b/src/test/java/com/jsoniter/TestMap.java index f9becb56..487b646d 100644 --- a/src/test/java/com/jsoniter/TestMap.java +++ b/src/test/java/com/jsoniter/TestMap.java @@ -41,6 +41,18 @@ public void test_integer_key() throws IOException { }}, map); } + public static enum EnumKey { + KeyA, KeyB + } + + public void test_enum_key() { + Map map = JsonIterator.deserialize("{\"KeyA\":null}", new TypeLiteral>() { + }); + assertEquals(new HashMap() {{ + put(EnumKey.KeyA, null); + }}, map); + } + public static class TestObject1 { public int Field; } diff --git a/src/test/java/com/jsoniter/TestOmitValue.java b/src/test/java/com/jsoniter/TestOmitValue.java new file mode 100644 index 00000000..80c11e76 --- /dev/null +++ b/src/test/java/com/jsoniter/TestOmitValue.java @@ -0,0 +1,137 @@ +package com.jsoniter; + +import com.jsoniter.spi.OmitValue.*; +import junit.framework.TestCase; + +public class TestOmitValue extends TestCase { + + public void test_shouldOmitInputPositiveOutputFalse() { + + // Arrange + final ZeroByte objectUnderTest = new ZeroByte(); + final Object val = (byte)1; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputPositiveOutputFalse2() { + + // Arrange + final ZeroInt objectUnderTest = new ZeroInt(); + final Object val = 1; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputPositiveOutputFalse3() { + + // Arrange + final ZeroLong objectUnderTest = new ZeroLong(); + final Object val = 1L; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputZeroOutputTrue() { + + // Arrange + final ZeroLong objectUnderTest = new ZeroLong(); + final Object val = 0L; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(true, retval); + } + + public void test_shouldOmitInputPositiveOutputFalse4() { + + // Arrange + final ZeroShort objectUnderTest = new ZeroShort(); + final Object val = (short)1; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputTrueOutputFalse() { + + // Arrange + final False objectUnderTest = new False(); + final Object val = true; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputNotNullOutputFalse() { + + // Arrange + final ZeroChar objectUnderTest = new ZeroChar(); + final Object val = '\u0001'; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputPositiveOutputFalse5() { + + // Arrange + final ZeroDouble objectUnderTest = new ZeroDouble(); + final Object val = 0x0.0000000000001p-1022 /* 4.94066e-324 */; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } + + public void test_shouldOmitInputZeroOutputTrue2() { + + // Arrange + final ZeroDouble objectUnderTest = new ZeroDouble(); + final Object val = 0.0; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(true, retval); + } + + public void test_shouldOmitInputPositiveOutputFalse6() { + + // Arrange + final ZeroFloat objectUnderTest = new ZeroFloat(); + final Object val = 0x1p-149f /* 1.4013e-45 */; + + // Act + final boolean retval = objectUnderTest.shouldOmit(val); + + // Assert result + assertEquals(false, retval); + } +} diff --git a/src/test/java/com/jsoniter/TestSlice.java b/src/test/java/com/jsoniter/TestSlice.java index 707c8e66..668372c6 100644 --- a/src/test/java/com/jsoniter/TestSlice.java +++ b/src/test/java/com/jsoniter/TestSlice.java @@ -19,4 +19,32 @@ public void test_hashcode() { assertEquals("hello", map.get(Slice.make("hello"))); assertEquals("world", map.get(Slice.make("world"))); } + + public void test_equalsInputNotNullOutputFalse2() { + + // Arrange + final byte[] byteArray = {(byte)2, (byte)1}; + final Slice objectUnderTest = new Slice(byteArray, 0, 1073741825); + final byte[] byteArray1 = {(byte)0}; + final Slice o = new Slice(byteArray1, 0, 1073741825); + + // Act + final boolean retval = objectUnderTest.equals(o); + + // Assert result + assertEquals(false, retval); + } + + public void test_equalsInputNotNullOutputFalse() { + + // Arrange + final Slice objectUnderTest = new Slice(null, 0, -2147483646); + final Slice o = new Slice(null, 0, 2); + + // Act + final boolean retval = objectUnderTest.equals(o); + + // Assert result + assertEquals(false, retval); + } } diff --git a/src/test/java/com/jsoniter/any/TestList.java b/src/test/java/com/jsoniter/any/TestList.java index 4cae6215..0dcd5799 100644 --- a/src/test/java/com/jsoniter/any/TestList.java +++ b/src/test/java/com/jsoniter/any/TestList.java @@ -1,10 +1,12 @@ package com.jsoniter.any; +import com.jsoniter.JsonIterator; import junit.framework.TestCase; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; public class TestList extends TestCase { public void test_size() { @@ -52,4 +54,10 @@ public void test_to_string() { any.asList().add(Any.wrap(4)); assertEquals("[1,2,3,4]", any.toString()); } + + public void test_for_each() { + Any a = JsonIterator.deserialize("[]"); + Iterator iter = a.iterator(); + assertFalse(iter.hasNext()); + } } diff --git a/src/test/java/com/jsoniter/any/TestLong.java b/src/test/java/com/jsoniter/any/TestLong.java new file mode 100644 index 00000000..247dbe8f --- /dev/null +++ b/src/test/java/com/jsoniter/any/TestLong.java @@ -0,0 +1,28 @@ +package com.jsoniter.any; + +import com.jsoniter.spi.JsonException; +import junit.framework.TestCase; + +public class TestLong extends TestCase { + public void test_to_string_should_trim() { + Any any = Any.lazyLong(" 1000".getBytes(), 0, " 1000".length()); + assertEquals("1000", any.toString()); + } + + public void test_should_fail_with_leading_zero() { + byte[] bytes = "01".getBytes(); + Any any = Any.lazyLong(bytes, 0, bytes.length); + try { + any.toLong(); + fail("This should fail."); + } catch (JsonException e) { + + } + } + + public void test_should_work_with_zero() { + byte[] bytes = "0".getBytes(); + Any any = Any.lazyLong(bytes, 0, bytes.length); + assertEquals(0L, any.toLong()); + } +} diff --git a/src/test/java/com/jsoniter/output/TestFloat.java b/src/test/java/com/jsoniter/output/TestFloat.java index 710458fd..faf72d31 100644 --- a/src/test/java/com/jsoniter/output/TestFloat.java +++ b/src/test/java/com/jsoniter/output/TestFloat.java @@ -8,4 +8,10 @@ public class TestFloat extends TestCase { public void testBigDecimal() { assertEquals("100.1", JsonStream.serialize(new BigDecimal("100.1"))); } + public void test_infinity() { + assertEquals("\"Infinity\"", JsonStream.serialize(Double.POSITIVE_INFINITY)); + assertEquals("\"Infinity\"", JsonStream.serialize(Float.POSITIVE_INFINITY)); + assertEquals("\"-Infinity\"", JsonStream.serialize(Double.NEGATIVE_INFINITY)); + assertEquals("\"-Infinity\"", JsonStream.serialize(Float.NEGATIVE_INFINITY)); + } } diff --git a/src/test/java/com/jsoniter/output/TestMap.java b/src/test/java/com/jsoniter/output/TestMap.java index 3b7aa5e4..2b91d6f0 100644 --- a/src/test/java/com/jsoniter/output/TestMap.java +++ b/src/test/java/com/jsoniter/output/TestMap.java @@ -8,6 +8,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; @@ -65,6 +66,19 @@ public void test_integer_key() throws IOException { assertEquals("{\"100\":null}", baos.toString()); } + public static enum EnumKey { + KeyA, KeyB + } + + public void test_enum_key() throws IOException { + HashMap obj = new HashMap(); + obj.put(EnumKey.KeyA, null); + stream.writeVal(new TypeLiteral>() { + }, obj); + stream.close(); + assertEquals("{\"KeyA\":null}", baos.toString()); + } + public static class TestObject1 { public int Field; } @@ -133,4 +147,13 @@ public void test_object_key() { m.put(1, 2); assertEquals("{\"1\":2}", JsonStream.serialize(m)); } + + public void test_multiple_keys() { + HashMap map = new HashMap(); + map.put("destination", "test_destination_value"); + map.put("amount", new BigDecimal("0.0000101101")); + map.put("password", "test_pass"); + final String serialized = JsonStream.serialize(map); + assertEquals(-1, serialized.indexOf("::")); + } } diff --git a/src/test/java/com/jsoniter/output/TestString.java b/src/test/java/com/jsoniter/output/TestString.java index 96dda062..186c770a 100644 --- a/src/test/java/com/jsoniter/output/TestString.java +++ b/src/test/java/com/jsoniter/output/TestString.java @@ -1,15 +1,40 @@ package com.jsoniter.output; import com.jsoniter.spi.Config; +import com.jsoniter.spi.Config.Builder; +import com.jsoniter.spi.JsoniterSpi; +import java.io.ByteArrayOutputStream; import junit.framework.TestCase; public class TestString extends TestCase { + + public static final String UTF8_GREETING = "Привет čau 你好 ~"; + public void test_unicode() { String output = JsonStream.serialize(new Config.Builder().escapeUnicode(false).build(), "中文"); assertEquals("\"中文\"", output); } + public void test_unicode_tilde() { + final String tilde = "~"; + String output = JsonStream.serialize(new Config.Builder().escapeUnicode(false).build(), tilde); + assertEquals("\""+tilde+"\"", output); + } + public void test_escape_unicode() { + final Config config = new Builder().escapeUnicode(false).build(); + + assertEquals("\""+UTF8_GREETING+"\"", JsonStream.serialize(config, UTF8_GREETING)); + assertEquals("\""+UTF8_GREETING+"\"", JsonStream.serialize(config.escapeUnicode(), UTF8_GREETING.getClass(), UTF8_GREETING)); + } public void test_escape_control_character() { String output = JsonStream.serialize(new String(new byte[]{0})); assertEquals("\"\\u0000\"", output); } + public void test_serialize_into_output_stream() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + boolean escapeUnicode = JsoniterSpi.getCurrentConfig().escapeUnicode(); + JsoniterSpi.setCurrentConfig(JsoniterSpi.getCurrentConfig().copyBuilder().escapeUnicode(false).build()); + JsonStream.serialize(String.class, UTF8_GREETING, baos); + JsoniterSpi.setCurrentConfig(JsoniterSpi.getCurrentConfig().copyBuilder().escapeUnicode(escapeUnicode).build()); + assertEquals("\"" + UTF8_GREETING + "\"", baos.toString()); + } } diff --git a/src/test/java/com/jsoniter/suite/AllTestCases.java b/src/test/java/com/jsoniter/suite/AllTestCases.java index 2fcf3136..d3196ed4 100644 --- a/src/test/java/com/jsoniter/suite/AllTestCases.java +++ b/src/test/java/com/jsoniter/suite/AllTestCases.java @@ -8,6 +8,7 @@ import com.jsoniter.TestObject; import com.jsoniter.TestString; import com.jsoniter.any.TestList; +import com.jsoniter.any.TestLong; import com.jsoniter.output.*; import com.jsoniter.output.TestInteger; import org.junit.runner.RunWith; @@ -52,8 +53,11 @@ TestGson.class, com.jsoniter.output.TestGson.class, TestStreamBuffer.class, + IterImplForStreamingTest.class, TestCollection.class, TestList.class, - TestAnnotationJsonObject.class}) + TestAnnotationJsonObject.class, + TestLong.class, + TestOmitValue.class}) public abstract class AllTestCases { }