diff --git a/CHANGELOG.md b/CHANGELOG.md index 47c23039..c93b3bdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +# 0.9.22 + +* fix #167 parse Object.class follow jackson convention. fixed the case of 1.0 parsed as int not double. +* fix #154 support map integer key +* fix #152 + +# 0.9.21 + +breaking changes + +* fix #149 parse Object.class follow jackson convention + +bug fixes + +* fix #145 add Any.registerEncoders +* merge #143 + +# 0.9.20 + +* fix #136, field with only getter is also considered as java bean property, so that @JsonIgnore on the field should be propagated to getter + # 0.9.19 * changed cfg class name to hashcode based * fix static codegen diff --git a/pom.xml b/pom.xml index 5a936199..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.21-SNAPSHOT + 0.9.24-SNAPSHOT jsoniter json iterator jsoniter (json-iterator) is fast and flexible JSON parser available in Java and Go @@ -46,38 +46,38 @@ 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 org.openjdk.jmh jmh-core - 1.17.3 + 1.20 test org.openjdk.jmh jmh-generator-annprocess - 1.17.3 + 1.20 test @@ -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 0b2922b9..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) { @@ -171,7 +167,7 @@ private static Type chooseImpl(Type type) { if (keyType == Object.class) { keyType = String.class; } - DefaultMapKeyDecoder.registerOrGetExisting(keyType); + MapKeyDecoders.registerOrGetExisting(keyType); return GenericsHelper.createParameterizedType(new Type[]{keyType, valueType}, null, clazz); } if (implClazz != null) { diff --git a/src/main/java/com/jsoniter/CodegenAccess.java b/src/main/java/com/jsoniter/CodegenAccess.java index 5bb972d1..bc4f3cb7 100644 --- a/src/main/java/com/jsoniter/CodegenAccess.java +++ b/src/main/java/com/jsoniter/CodegenAccess.java @@ -142,9 +142,12 @@ public static final Slice readSlice(JsonIterator iter) throws IOException { } public static final Object readMapKey(String cacheKey, JsonIterator iter) throws IOException { - Slice encodedMapKey = readObjectFieldAsSlice(iter); - MapKeyDecoder mapKeyDecoder = JsoniterSpi.getMapKeyDecoder(cacheKey); - return mapKeyDecoder.decode(encodedMapKey); + Decoder mapKeyDecoder = JsoniterSpi.getMapKeyDecoder(cacheKey); + Object key = mapKeyDecoder.decode(iter); + if (IterImpl.nextToken(iter) != ':') { + throw iter.reportError("readMapKey", "expect :"); + } + return key; } final static boolean skipWhitespacesWithoutLoadMore(JsonIterator iter) throws IOException { diff --git a/src/main/java/com/jsoniter/DefaultMapKeyDecoder.java b/src/main/java/com/jsoniter/DefaultMapKeyDecoder.java deleted file mode 100644 index 786a2270..00000000 --- a/src/main/java/com/jsoniter/DefaultMapKeyDecoder.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.jsoniter; - -import com.jsoniter.spi.*; - -import java.io.IOException; -import java.lang.reflect.Type; - -class DefaultMapKeyDecoder implements MapKeyDecoder { - - public static MapKeyDecoder registerOrGetExisting(Type mapKeyType) { - String cacheKey = JsoniterSpi.getMapKeyDecoderCacheKey(mapKeyType); - MapKeyDecoder mapKeyDecoder = JsoniterSpi.getMapKeyDecoder(cacheKey); - if (null != mapKeyDecoder) { - return mapKeyDecoder; - } - mapKeyDecoder = new DefaultMapKeyDecoder(TypeLiteral.create(mapKeyType)); - JsoniterSpi.addNewMapDecoder(cacheKey, mapKeyDecoder); - return mapKeyDecoder; - } - - private final TypeLiteral mapKeyTypeLiteral; - - private DefaultMapKeyDecoder(TypeLiteral mapKeyTypeLiteral) { - this.mapKeyTypeLiteral = mapKeyTypeLiteral; - } - - @Override - public Object decode(Slice encodedMapKey) { - JsonIterator iter = JsonIteratorPool.borrowJsonIterator(); - iter.reset(encodedMapKey); - try { - return iter.read(mapKeyTypeLiteral); - } catch (IOException e) { - throw new JsonException(e); - } finally { - JsonIteratorPool.returnJsonIterator(iter); - } - } -} diff --git a/src/main/java/com/jsoniter/IterImpl.java b/src/main/java/com/jsoniter/IterImpl.java index 8a5071b6..ad779fd8 100644 --- a/src/main/java/com/jsoniter/IterImpl.java +++ b/src/main/java/com/jsoniter/IterImpl.java @@ -5,9 +5,15 @@ import com.jsoniter.spi.Slice; import java.io.IOException; +import java.math.BigInteger; class IterImpl { + private static BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE); + private static BigInteger minLong = BigInteger.valueOf(Long.MIN_VALUE); + private static BigInteger maxInt = BigInteger.valueOf(Integer.MAX_VALUE); + private static BigInteger minInt = BigInteger.valueOf(Integer.MIN_VALUE); + public static final int readObjectFieldAsHash(JsonIterator iter) throws IOException { if (readByte(iter) != '"') { if (nextToken(iter) != '"') { @@ -53,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 @@ -79,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 @@ -119,7 +125,7 @@ final static boolean skipNumber(JsonIterator iter) throws IOException { boolean dotFound = false; for (int i = iter.head; i < iter.tail; i++) { byte c = iter.buf[i]; - if (c == '.') { + if (c == '.' || c == 'e' || c == 'E') { dotFound = true; continue; } @@ -387,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 9cbf7485..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; @@ -179,7 +180,7 @@ final static boolean skipNumber(JsonIterator iter) throws IOException { for (; ; ) { for (int i = iter.head; i < iter.tail; i++) { byte c = iter.buf[i]; - if (c == '.') { + if (c == '.' || c == 'e' || c == 'E') { dotFound = true; continue; } @@ -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)) { @@ -539,15 +545,32 @@ static int readIntSlowPath(final JsonIterator iter, int value) throws IOExceptio public static final double readDoubleSlowPath(final JsonIterator iter) throws IOException { try { - String numberAsStr = readNumber(iter); - return Double.valueOf(numberAsStr); + 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()); } } - public static final String readNumber(final JsonIterator iter) throws IOException { + static class numberChars { + char[] chars; + int charsLength; + boolean dotFound; + } + + public static final numberChars readNumber(final JsonIterator iter) throws IOException { int j = 0; + boolean dotFound = false; for (; ; ) { for (int i = iter.head; i < iter.tail; i++) { if (j == iter.reusableChars.length) { @@ -557,11 +580,13 @@ public static final String readNumber(final JsonIterator iter) throws IOExceptio } byte c = iter.buf[i]; switch (c) { - case '-': - case '+': case '.': case 'e': case 'E': + dotFound = true; + // fallthrough + case '-': + case '+': case '0': case '1': case '2': @@ -576,12 +601,20 @@ public static final String readNumber(final JsonIterator iter) throws IOExceptio break; default: iter.head = i; - return new String(iter.reusableChars, 0, j); + numberChars numberChars = new numberChars(); + numberChars.chars = iter.reusableChars; + numberChars.charsLength = j; + numberChars.dotFound = dotFound; + return numberChars; } } if (!IterImpl.loadMore(iter)) { iter.head = iter.tail; - return new String(iter.reusableChars, 0, j); + numberChars numberChars = new numberChars(); + numberChars.chars = iter.reusableChars; + numberChars.charsLength = j; + numberChars.dotFound = dotFound; + return numberChars; } } } @@ -616,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/IterImplString.java b/src/main/java/com/jsoniter/IterImplString.java index 573cd2d1..c6a0b452 100644 --- a/src/main/java/com/jsoniter/IterImplString.java +++ b/src/main/java/com/jsoniter/IterImplString.java @@ -59,7 +59,7 @@ public static final String readString(JsonIterator iter) throws IOException { IterImpl.skipFixedBytes(iter, 3); return null; } - iter.reportError("readString", "expect string or null, but " + (char) c); + throw iter.reportError("readString", "expect string or null, but " + (char) c); } int j = parse(iter); return new String(iter.reusableChars, 0, j); diff --git a/src/main/java/com/jsoniter/JsonIterator.java b/src/main/java/com/jsoniter/JsonIterator.java index 0f93c4e2..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) { @@ -190,7 +202,8 @@ public final boolean readArray() throws IOException { } public String readNumberAsString() throws IOException { - return IterImplForStreaming.readNumber(this); + IterImplForStreaming.numberChars numberChars = IterImplForStreaming.readNumber(this); + return new String(numberChars.chars, 0, numberChars.charsLength); } public static interface ReadArrayCallback { @@ -239,7 +252,8 @@ public final BigDecimal readBigDecimal() throws IOException { if (valueType != ValueType.NUMBER) { throw reportError("readBigDecimal", "not number"); } - return new BigDecimal(IterImplForStreaming.readNumber(this)); + IterImplForStreaming.numberChars numberChars = IterImplForStreaming.readNumber(this); + return new BigDecimal(numberChars.chars, 0, numberChars.charsLength); } public final BigInteger readBigInteger() throws IOException { @@ -252,7 +266,8 @@ public final BigInteger readBigInteger() throws IOException { if (valueType != ValueType.NUMBER) { throw reportError("readBigDecimal", "not number"); } - return new BigInteger(IterImplForStreaming.readNumber(this)); + IterImplForStreaming.numberChars numberChars = IterImplForStreaming.readNumber(this); + return new BigInteger(new String(numberChars.chars, 0, numberChars.charsLength)); } public final Any readAny() throws IOException { @@ -288,7 +303,21 @@ public final Object read() throws IOException { case STRING: return readString(); case NUMBER: - return readDouble(); + IterImplForStreaming.numberChars numberChars = IterImplForStreaming.readNumber(this); + 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.valueOf(numberStr); + if (longNumber <= Integer.MAX_VALUE && longNumber >= Integer.MIN_VALUE) { + return (int) longNumber; + } + return longNumber; + } + return number; case NULL: IterImpl.skipFixedBytes(this, 4); return null; 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 new file mode 100644 index 00000000..0bb75a30 --- /dev/null +++ b/src/main/java/com/jsoniter/MapKeyDecoders.java @@ -0,0 +1,77 @@ +package com.jsoniter; + +import com.jsoniter.spi.*; + +import java.io.IOException; +import java.lang.reflect.Type; + +class MapKeyDecoders { + + public static Decoder registerOrGetExisting(Type mapKeyType) { + String cacheKey = JsoniterSpi.getMapKeyDecoderCacheKey(mapKeyType); + Decoder mapKeyDecoder = JsoniterSpi.getMapKeyDecoder(cacheKey); + if (null != mapKeyDecoder) { + return mapKeyDecoder; + } + mapKeyDecoder = createMapKeyDecoder(mapKeyType); + JsoniterSpi.addNewMapDecoder(cacheKey, mapKeyDecoder); + return mapKeyDecoder; + } + + 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 decode map key type: " + mapKeyType); + } + + private static class StringKeyDecoder implements Decoder { + + @Override + public Object decode(JsonIterator iter) throws IOException { + return iter.readString(); + } + } + + 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; + + private NumberKeyDecoder(Decoder decoder) { + this.decoder = decoder; + } + + @Override + public Object decode(JsonIterator iter) throws IOException { + if (IterImpl.nextToken(iter) != '"') { + throw iter.reportError("decode number map key", "expect \""); + } + Object key = decoder.decode(iter); + if (IterImpl.nextToken(iter) != '"') { + throw iter.reportError("decode number map key", "expect \""); + } + return key; + } + } +} diff --git a/src/main/java/com/jsoniter/ReflectionMapDecoder.java b/src/main/java/com/jsoniter/ReflectionMapDecoder.java index 5010371c..7e5f220a 100644 --- a/src/main/java/com/jsoniter/ReflectionMapDecoder.java +++ b/src/main/java/com/jsoniter/ReflectionMapDecoder.java @@ -11,7 +11,7 @@ class ReflectionMapDecoder implements Decoder { private final Constructor ctor; private final Decoder valueTypeDecoder; - private final MapKeyDecoder mapKeyDecoder; + private final Decoder mapKeyDecoder; public ReflectionMapDecoder(Class clazz, Type[] typeArgs) { try { @@ -20,11 +20,7 @@ public ReflectionMapDecoder(Class clazz, Type[] typeArgs) { throw new JsonException(e); } Type keyType = typeArgs[0]; - if (keyType == String.class) { - mapKeyDecoder = null; - } else { - mapKeyDecoder = DefaultMapKeyDecoder.registerOrGetExisting(keyType); - } + mapKeyDecoder = MapKeyDecoders.registerOrGetExisting(keyType); TypeLiteral valueTypeLiteral = TypeLiteral.create(typeArgs[1]); valueTypeDecoder = Codegen.getDecoder(valueTypeLiteral.getDecoderCacheKey(), typeArgs[1]); } @@ -59,11 +55,10 @@ private Object decode_(JsonIterator iter) throws Exception { } private Object readMapKey(JsonIterator iter) throws IOException { - if (mapKeyDecoder == null) { - return CodegenAccess.readObjectFieldAsString(iter); - } else { - Slice mapKey = CodegenAccess.readObjectFieldAsSlice(iter); - return mapKeyDecoder.decode(mapKey); + Object key = mapKeyDecoder.decode(iter); + if (':' != IterImpl.nextToken(iter)) { + throw iter.reportError("readMapKey", "expect :"); } + return key; } } diff --git a/src/main/java/com/jsoniter/any/Any.java b/src/main/java/com/jsoniter/any/Any.java index e9dfe90e..8159f1c2 100644 --- a/src/main/java/com/jsoniter/any/Any.java +++ b/src/main/java/com/jsoniter/any/Any.java @@ -8,6 +8,8 @@ import com.jsoniter.spi.TypeLiteral; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.*; public abstract class Any implements Iterable { @@ -174,6 +176,14 @@ public final double toDouble(Object... keys) { public abstract double toDouble(); + public final BigInteger toBigInteger(Object ...keys) { return get(keys).toBigInteger(); } + + public abstract BigInteger toBigInteger(); + + public final BigDecimal toBigDecimal(Object ...keys) { return get(keys).toBigDecimal(); } + + public abstract BigDecimal toBigDecimal(); + public final String toString(Object... keys) { return get(keys).toString(); } diff --git a/src/main/java/com/jsoniter/any/ArrayAny.java b/src/main/java/com/jsoniter/any/ArrayAny.java index 55704b89..50d5c174 100644 --- a/src/main/java/com/jsoniter/any/ArrayAny.java +++ b/src/main/java/com/jsoniter/any/ArrayAny.java @@ -4,6 +4,8 @@ import com.jsoniter.output.JsonStream; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -115,4 +117,14 @@ public float toFloat() { public double toDouble() { return val.size(); } + + @Override + public BigInteger toBigInteger() { + return BigInteger.valueOf(val.size()); + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.valueOf(val.size()); + } } diff --git a/src/main/java/com/jsoniter/any/ArrayLazyAny.java b/src/main/java/com/jsoniter/any/ArrayLazyAny.java index cc452968..13983641 100644 --- a/src/main/java/com/jsoniter/any/ArrayLazyAny.java +++ b/src/main/java/com/jsoniter/any/ArrayLazyAny.java @@ -6,6 +6,8 @@ import com.jsoniter.spi.TypeLiteral; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -65,6 +67,16 @@ public double toDouble() { return size(); } + @Override + public BigInteger toBigInteger() { + return BigInteger.valueOf(size()); + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.valueOf(size()); + } + @Override public int size() { fillCache(); @@ -194,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/ArrayWrapperAny.java b/src/main/java/com/jsoniter/any/ArrayWrapperAny.java index fa2fb99b..f5693663 100644 --- a/src/main/java/com/jsoniter/any/ArrayWrapperAny.java +++ b/src/main/java/com/jsoniter/any/ArrayWrapperAny.java @@ -5,6 +5,8 @@ import java.io.IOException; import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -54,6 +56,16 @@ public double toDouble() { return size(); } + @Override + public BigInteger toBigInteger() { + return BigInteger.valueOf(size()); + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.valueOf(size()); + } + @Override public String toString() { if (cache == null) { diff --git a/src/main/java/com/jsoniter/any/DoubleAny.java b/src/main/java/com/jsoniter/any/DoubleAny.java index 9cd4c31f..64bcb88d 100644 --- a/src/main/java/com/jsoniter/any/DoubleAny.java +++ b/src/main/java/com/jsoniter/any/DoubleAny.java @@ -4,6 +4,8 @@ import com.jsoniter.output.JsonStream; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; class DoubleAny extends Any { @@ -48,6 +50,16 @@ public double toDouble() { return val; } + @Override + public BigInteger toBigInteger() { + return BigInteger.valueOf((long) val); + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.valueOf(val); + } + @Override public String toString() { return String.valueOf(val); diff --git a/src/main/java/com/jsoniter/any/DoubleLazyAny.java b/src/main/java/com/jsoniter/any/DoubleLazyAny.java index 1cc496cf..edc32774 100644 --- a/src/main/java/com/jsoniter/any/DoubleLazyAny.java +++ b/src/main/java/com/jsoniter/any/DoubleLazyAny.java @@ -6,6 +6,8 @@ import com.jsoniter.ValueType; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; class DoubleLazyAny extends LazyAny { @@ -57,6 +59,16 @@ public double toDouble() { return cache; } + @Override + public BigInteger toBigInteger() { + return new BigInteger(toString()); + } + + @Override + public BigDecimal toBigDecimal() { + return new BigDecimal(toString()); + } + private void fillCache() { if (!isCached) { JsonIterator iter = parse(); diff --git a/src/main/java/com/jsoniter/any/FalseAny.java b/src/main/java/com/jsoniter/any/FalseAny.java index a516e96e..6d7a7288 100644 --- a/src/main/java/com/jsoniter/any/FalseAny.java +++ b/src/main/java/com/jsoniter/any/FalseAny.java @@ -4,6 +4,8 @@ import com.jsoniter.output.JsonStream; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; class FalseAny extends Any { @@ -44,6 +46,16 @@ public double toDouble() { return 0; } + @Override + public BigInteger toBigInteger() { + return BigInteger.ZERO; + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.ZERO; + } + @Override public String toString() { return "false"; diff --git a/src/main/java/com/jsoniter/any/FloatAny.java b/src/main/java/com/jsoniter/any/FloatAny.java index b6d29f03..7063f321 100644 --- a/src/main/java/com/jsoniter/any/FloatAny.java +++ b/src/main/java/com/jsoniter/any/FloatAny.java @@ -5,6 +5,8 @@ import com.jsoniter.spi.TypeLiteral; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; class FloatAny extends Any { @@ -49,6 +51,16 @@ public double toDouble() { return val; } + @Override + public BigInteger toBigInteger() { + return BigInteger.valueOf((long) val); + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.valueOf(val); + } + @Override public String toString() { return String.valueOf(val); diff --git a/src/main/java/com/jsoniter/any/IntAny.java b/src/main/java/com/jsoniter/any/IntAny.java index ff408443..fbb5f074 100644 --- a/src/main/java/com/jsoniter/any/IntAny.java +++ b/src/main/java/com/jsoniter/any/IntAny.java @@ -4,6 +4,8 @@ import com.jsoniter.output.JsonStream; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; class IntAny extends Any { @@ -48,6 +50,16 @@ public double toDouble() { return val; } + @Override + public BigInteger toBigInteger() { + return BigInteger.valueOf(val); + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.valueOf(val); + } + @Override public String toString() { return String.valueOf(val); 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/any/ListWrapperAny.java b/src/main/java/com/jsoniter/any/ListWrapperAny.java index 7e0e9ca9..44345148 100644 --- a/src/main/java/com/jsoniter/any/ListWrapperAny.java +++ b/src/main/java/com/jsoniter/any/ListWrapperAny.java @@ -4,6 +4,8 @@ import com.jsoniter.output.JsonStream; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -53,6 +55,16 @@ public double toDouble() { return size(); } + @Override + public BigInteger toBigInteger() { + return BigInteger.valueOf(size()); + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.valueOf(size()); + } + @Override public String toString() { if (cache == null) { diff --git a/src/main/java/com/jsoniter/any/LongAny.java b/src/main/java/com/jsoniter/any/LongAny.java index 3245b93e..76f683eb 100644 --- a/src/main/java/com/jsoniter/any/LongAny.java +++ b/src/main/java/com/jsoniter/any/LongAny.java @@ -4,6 +4,8 @@ import com.jsoniter.output.JsonStream; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; class LongAny extends Any { @@ -48,6 +50,16 @@ public double toDouble() { return val; } + @Override + public BigInteger toBigInteger() { + return BigInteger.valueOf(val); + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.valueOf(val); + } + @Override public String toString() { return String.valueOf(val); diff --git a/src/main/java/com/jsoniter/any/LongLazyAny.java b/src/main/java/com/jsoniter/any/LongLazyAny.java index 3ec38134..3a60dc74 100644 --- a/src/main/java/com/jsoniter/any/LongLazyAny.java +++ b/src/main/java/com/jsoniter/any/LongLazyAny.java @@ -6,6 +6,8 @@ import com.jsoniter.ValueType; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; class LongLazyAny extends LazyAny { @@ -57,6 +59,16 @@ public double toDouble() { return cache; } + @Override + public BigInteger toBigInteger() { + return new BigInteger(toString()); + } + + @Override + public BigDecimal toBigDecimal() { + return new BigDecimal(toString()); + } + private void fillCache() { if (!isCached) { JsonIterator iter = parse(); diff --git a/src/main/java/com/jsoniter/any/MapWrapperAny.java b/src/main/java/com/jsoniter/any/MapWrapperAny.java index 92a8cc98..5897ba1e 100644 --- a/src/main/java/com/jsoniter/any/MapWrapperAny.java +++ b/src/main/java/com/jsoniter/any/MapWrapperAny.java @@ -4,6 +4,8 @@ import com.jsoniter.output.JsonStream; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -54,6 +56,16 @@ public double toDouble() { return size(); } + @Override + public BigInteger toBigInteger() { + return BigInteger.valueOf(size()); + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.valueOf(size()); + } + @Override public String toString() { if (cache == null) { diff --git a/src/main/java/com/jsoniter/any/NotFoundAny.java b/src/main/java/com/jsoniter/any/NotFoundAny.java index 0722aec6..1a5439ea 100644 --- a/src/main/java/com/jsoniter/any/NotFoundAny.java +++ b/src/main/java/com/jsoniter/any/NotFoundAny.java @@ -5,6 +5,8 @@ import com.jsoniter.spi.JsonException; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.Arrays; class NotFoundAny extends Any { @@ -81,6 +83,16 @@ public double toDouble() { return 0; } + @Override + public BigInteger toBigInteger() { + return BigInteger.ZERO; + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.ZERO; + } + @Override public String toString() { return ""; diff --git a/src/main/java/com/jsoniter/any/NullAny.java b/src/main/java/com/jsoniter/any/NullAny.java index d17e3bd2..e086a34b 100644 --- a/src/main/java/com/jsoniter/any/NullAny.java +++ b/src/main/java/com/jsoniter/any/NullAny.java @@ -4,6 +4,8 @@ import com.jsoniter.output.JsonStream; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; class NullAny extends Any { @@ -44,6 +46,16 @@ public double toDouble() { return 0; } + @Override + public BigInteger toBigInteger() { + return BigInteger.ZERO; + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.ZERO; + } + @Override public void writeTo(JsonStream stream) throws IOException { stream.writeNull(); diff --git a/src/main/java/com/jsoniter/any/ObjectAny.java b/src/main/java/com/jsoniter/any/ObjectAny.java index 4532c6f6..009a4f13 100644 --- a/src/main/java/com/jsoniter/any/ObjectAny.java +++ b/src/main/java/com/jsoniter/any/ObjectAny.java @@ -4,6 +4,8 @@ import com.jsoniter.output.JsonStream; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -67,6 +69,16 @@ public double toDouble() { return size(); } + @Override + public BigInteger toBigInteger() { + return BigInteger.valueOf(size()); + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.valueOf(size()); + } + @Override public String toString() { return JsonStream.serialize(this); diff --git a/src/main/java/com/jsoniter/any/ObjectLazyAny.java b/src/main/java/com/jsoniter/any/ObjectLazyAny.java index 3aaf1b28..1fb389b6 100644 --- a/src/main/java/com/jsoniter/any/ObjectLazyAny.java +++ b/src/main/java/com/jsoniter/any/ObjectLazyAny.java @@ -6,6 +6,8 @@ import com.jsoniter.spi.TypeLiteral; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -67,6 +69,16 @@ public double toDouble() { return size(); } + @Override + public BigInteger toBigInteger() { + return BigInteger.valueOf(size()); + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.valueOf(size()); + } + @Override public int size() { fillCache(); diff --git a/src/main/java/com/jsoniter/any/StringAny.java b/src/main/java/com/jsoniter/any/StringAny.java index db0412b1..ccd9c090 100644 --- a/src/main/java/com/jsoniter/any/StringAny.java +++ b/src/main/java/com/jsoniter/any/StringAny.java @@ -4,6 +4,8 @@ import com.jsoniter.output.JsonStream; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; class StringAny extends Any { @@ -77,6 +79,16 @@ public double toDouble() { return Double.valueOf(val); } + @Override + public BigInteger toBigInteger() { + return new BigInteger(val); + } + + @Override + public BigDecimal toBigDecimal() { + return new BigDecimal(val); + } + @Override public String toString() { return val; diff --git a/src/main/java/com/jsoniter/any/StringLazyAny.java b/src/main/java/com/jsoniter/any/StringLazyAny.java index 79d33f88..4e9f3bab 100644 --- a/src/main/java/com/jsoniter/any/StringLazyAny.java +++ b/src/main/java/com/jsoniter/any/StringLazyAny.java @@ -7,6 +7,8 @@ import com.jsoniter.spi.JsonException; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; class StringLazyAny extends LazyAny { private final static String FALSE = "false"; @@ -103,6 +105,16 @@ public double toDouble() { } } + @Override + public BigInteger toBigInteger() { + return new BigInteger(toString()); + } + + @Override + public BigDecimal toBigDecimal() { + return new BigDecimal(toString()); + } + @Override public String toString() { fillCache(); diff --git a/src/main/java/com/jsoniter/any/TrueAny.java b/src/main/java/com/jsoniter/any/TrueAny.java index 6163d0cd..2511f4f2 100644 --- a/src/main/java/com/jsoniter/any/TrueAny.java +++ b/src/main/java/com/jsoniter/any/TrueAny.java @@ -4,6 +4,8 @@ import com.jsoniter.output.JsonStream; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; class TrueAny extends Any { @@ -44,6 +46,16 @@ public double toDouble() { return 1; } + @Override + public BigInteger toBigInteger() { + return BigInteger.ONE; + } + + @Override + public BigDecimal toBigDecimal() { + return BigDecimal.ONE; + } + @Override public String toString() { return "true"; 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/Codegen.java b/src/main/java/com/jsoniter/output/Codegen.java index 569e8feb..7680244d 100644 --- a/src/main/java/com/jsoniter/output/Codegen.java +++ b/src/main/java/com/jsoniter/output/Codegen.java @@ -79,7 +79,7 @@ private static synchronized Encoder gen(final String cacheKey, Type type) { } ClassInfo classInfo = new ClassInfo(type); if (Map.class.isAssignableFrom(classInfo.clazz) && classInfo.typeArgs.length > 1) { - DefaultMapKeyEncoder.registerOrGetExisting(classInfo.typeArgs[0]); + MapKeyEncoders.registerOrGetExisting(classInfo.typeArgs[0]); } if (mode == EncodingMode.REFLECTION_MODE) { encoder = ReflectionEncoderFactory.create(classInfo); diff --git a/src/main/java/com/jsoniter/output/CodegenAccess.java b/src/main/java/com/jsoniter/output/CodegenAccess.java index 8b9ebfce..913df649 100644 --- a/src/main/java/com/jsoniter/output/CodegenAccess.java +++ b/src/main/java/com/jsoniter/output/CodegenAccess.java @@ -53,8 +53,8 @@ public static void writeVal(String cacheKey, double obj, JsonStream stream) thro } public static void writeMapKey(String cacheKey, Object mapKey, JsonStream stream) throws IOException { - String encodedMapKey = JsoniterSpi.getMapKeyEncoder(cacheKey).encode(mapKey); - stream.writeVal(encodedMapKey); + Encoder mapKeyEncoder = JsoniterSpi.getMapKeyEncoder(cacheKey); + mapKeyEncoder.encode(mapKey, stream); } public static void writeStringWithoutQuote(String obj, JsonStream stream) throws IOException { diff --git a/src/main/java/com/jsoniter/output/CodegenImplMap.java b/src/main/java/com/jsoniter/output/CodegenImplMap.java index 245d27c7..006817d3 100644 --- a/src/main/java/com/jsoniter/output/CodegenImplMap.java +++ b/src/main/java/com/jsoniter/output/CodegenImplMap.java @@ -13,13 +13,12 @@ public static CodegenResult genMap(String cacheKey, ClassInfo classInfo) { if (cacheKey.endsWith("__value_not_nullable")) { isCollectionValueNullable = false; } - Type keyType = String.class; + Type keyType = Object.class; Type valueType = Object.class; if (typeArgs.length == 2) { keyType = typeArgs[0]; valueType = typeArgs[1]; } - String mapCacheKey = JsoniterSpi.getMapKeyEncoderCacheKey(keyType); CodegenResult ctx = new CodegenResult(); ctx.append("public static void encode_(java.lang.Object obj, com.jsoniter.output.JsonStream stream) throws java.io.IOException {"); ctx.append("if (obj == null) { stream.writeNull(); return; }"); @@ -36,16 +35,7 @@ public static CodegenResult genMap(String cacheKey, ClassInfo classInfo) { } else { ctx.append("stream.writeObjectStart(); stream.writeIndention();"); } - if (keyType == String.class) { - ctx.append("stream.writeVal((java.lang.String)entry.getKey());"); - } else { - ctx.append(String.format("com.jsoniter.output.CodegenAccess.writeMapKey(\"%s\", entry.getKey(), stream);", mapCacheKey)); - } - if (noIndention) { - ctx.append("stream.write(':');"); - } else { - ctx.append("stream.write((byte)':', (byte)' ');"); - } + genWriteMapKey(ctx, keyType, noIndention); if (isCollectionValueNullable) { ctx.append("if (entry.getValue() == null) { stream.writeNull(); } else {"); CodegenImplNative.genWriteOp(ctx, "entry.getValue()", valueType, true); @@ -60,16 +50,7 @@ public static CodegenResult genMap(String cacheKey, ClassInfo classInfo) { } else { ctx.append("stream.writeMore();"); } - if (keyType == String.class) { - ctx.append("stream.writeVal((java.lang.String)entry.getKey());"); - } else { - ctx.append(String.format("com.jsoniter.output.CodegenAccess.writeMapKey(\"%s\", entry.getKey(), stream);", mapCacheKey)); - } - if (noIndention) { - ctx.append("stream.write(':');"); - } else { - ctx.append("stream.write((byte)':', (byte)' ');"); - } + genWriteMapKey(ctx, keyType, noIndention); if (isCollectionValueNullable) { ctx.append("if (entry.getValue() == null) { stream.writeNull(); } else {"); CodegenImplNative.genWriteOp(ctx, "entry.getValue()", valueType, true); @@ -86,4 +67,26 @@ public static CodegenResult genMap(String cacheKey, ClassInfo classInfo) { ctx.append("}"); return ctx; } + + private static void genWriteMapKey(CodegenResult ctx, Type keyType, boolean noIndention) { + if (keyType == Object.class) { + ctx.append("stream.writeObjectField(entry.getKey());"); + return; + } + if (keyType == String.class) { + ctx.append("stream.writeVal((java.lang.String)entry.getKey());"); + } else if (CodegenImplNative.NATIVE_ENCODERS.containsKey(keyType)) { + ctx.append("stream.write('\"');"); + ctx.append(String.format("stream.writeVal((%s)entry.getKey());", CodegenImplNative.getTypeName(keyType))); + ctx.append("stream.write('\"');"); + } else { + String mapCacheKey = JsoniterSpi.getMapKeyEncoderCacheKey(keyType); + ctx.append(String.format("com.jsoniter.output.CodegenAccess.writeMapKey(\"%s\", entry.getKey(), stream);", mapCacheKey)); + } + if (noIndention) { + ctx.append("stream.write(':');"); + } else { + ctx.append("stream.write((byte)':', (byte)' ');"); + } + } } diff --git a/src/main/java/com/jsoniter/output/DefaultMapKeyEncoder.java b/src/main/java/com/jsoniter/output/DefaultMapKeyEncoder.java deleted file mode 100644 index 52357495..00000000 --- a/src/main/java/com/jsoniter/output/DefaultMapKeyEncoder.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.jsoniter.output; - -import com.jsoniter.spi.*; - -import java.lang.reflect.Type; - -class DefaultMapKeyEncoder implements MapKeyEncoder { - - public static MapKeyEncoder registerOrGetExisting(Type mapKeyType) { - String cacheKey = JsoniterSpi.getMapKeyEncoderCacheKey(mapKeyType); - MapKeyEncoder mapKeyEncoder = JsoniterSpi.getMapKeyEncoder(cacheKey); - if (null != mapKeyEncoder) { - return mapKeyEncoder; - } - mapKeyEncoder = new DefaultMapKeyEncoder(); - JsoniterSpi.addNewMapEncoder(cacheKey, mapKeyEncoder); - return mapKeyEncoder; - } - - @Override - public String encode(Object mapKey) { - return mapKey.toString(); - } -} diff --git a/src/main/java/com/jsoniter/output/JsonStream.java b/src/main/java/com/jsoniter/output/JsonStream.java index d1e33670..7886bc05 100644 --- a/src/main/java/com/jsoniter/output/JsonStream.java +++ b/src/main/java/com/jsoniter/output/JsonStream.java @@ -331,6 +331,20 @@ public final void writeObjectField(String field) throws IOException { } } + public final void writeObjectField(Object key) throws IOException { + Encoder encoder = MapKeyEncoders.registerOrGetExisting(key.getClass()); + writeObjectField(key, encoder); + } + + public final void writeObjectField(Object key, Encoder keyEncoder) throws IOException { + keyEncoder.encode(key, this); + if (indention > 0) { + write((byte) ':', (byte) ' '); + } else { + write(':'); + } + } + public final void writeObjectEnd() throws IOException { int indentionStep = currentConfig().indentionStep(); writeIndention(indentionStep); @@ -428,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 new file mode 100644 index 00000000..401ebfbe --- /dev/null +++ b/src/main/java/com/jsoniter/output/MapKeyEncoders.java @@ -0,0 +1,78 @@ +package com.jsoniter.output; + +import com.jsoniter.spi.*; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; + +class MapKeyEncoders { + + public static Encoder registerOrGetExisting(Type mapKeyType) { + String cacheKey = JsoniterSpi.getMapKeyEncoderCacheKey(mapKeyType); + Encoder mapKeyEncoder = JsoniterSpi.getMapKeyEncoder(cacheKey); + if (null != mapKeyEncoder) { + return mapKeyEncoder; + } + mapKeyEncoder = createDefaultEncoder(mapKeyType); + JsoniterSpi.addNewMapEncoder(cacheKey, mapKeyEncoder); + return mapKeyEncoder; + } + + private static Encoder createDefaultEncoder(Type mapKeyType) { + if (mapKeyType == String.class) { + return new StringKeyEncoder(); + } + if (mapKeyType == Object.class) { + return new DynamicKeyEncoder(); + } + 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); + } + throw new JsonException("can not encode map key type: " + mapKeyType); + } + + private static class StringKeyEncoder implements Encoder { + + @Override + public void encode(Object obj, JsonStream stream) throws IOException { + stream.writeVal(obj); + } + } + + private static class NumberKeyEncoder implements Encoder { + + private final Encoder encoder; + + private NumberKeyEncoder(Encoder encoder) { + this.encoder = encoder; + } + + @Override + public void encode(Object obj, JsonStream stream) throws IOException { + stream.write('"'); + encoder.encode(obj, stream); + stream.write('"'); + } + } + + private static class DynamicKeyEncoder implements Encoder { + + @Override + public void encode(Object obj, JsonStream stream) throws IOException { + Class clazz = obj.getClass(); + if (clazz == Object.class) { + throw new JsonException("map key type is Object.class, can not be encoded"); + } + Encoder mapKeyEncoder = registerOrGetExisting(clazz); + mapKeyEncoder.encode(obj, stream); + } + } +} diff --git a/src/main/java/com/jsoniter/output/ReflectionMapEncoder.java b/src/main/java/com/jsoniter/output/ReflectionMapEncoder.java index a4380737..ddf27d65 100644 --- a/src/main/java/com/jsoniter/output/ReflectionMapEncoder.java +++ b/src/main/java/com/jsoniter/output/ReflectionMapEncoder.java @@ -11,20 +11,16 @@ class ReflectionMapEncoder implements Encoder.ReflectionEncoder { private final TypeLiteral valueTypeLiteral; - private final MapKeyEncoder mapKeyEncoder; + private final Encoder mapKeyEncoder; public ReflectionMapEncoder(Class clazz, Type[] typeArgs) { - Type keyType = String.class; + Type keyType = Object.class; Type valueType = Object.class; if (typeArgs.length == 2) { keyType = typeArgs[0]; valueType = typeArgs[1]; } - if (keyType == String.class) { - mapKeyEncoder = null; - } else { - mapKeyEncoder = DefaultMapKeyEncoder.registerOrGetExisting(keyType); - } + mapKeyEncoder = MapKeyEncoders.registerOrGetExisting(keyType); valueTypeLiteral = TypeLiteral.create(valueType); } @@ -58,11 +54,7 @@ private boolean writeEntry(JsonStream stream, boolean notFirst, Map.Entry 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/Config.java b/src/main/java/com/jsoniter/spi/Config.java index fbd66fbf..5b0d6c98 100644 --- a/src/main/java/com/jsoniter/spi/Config.java +++ b/src/main/java/com/jsoniter/spi/Config.java @@ -277,6 +277,12 @@ private void detectWrappers(ClassDescriptor desc, List allMethods) { } Annotation[][] annotations = method.getParameterAnnotations(); String[] paramNames = getParamNames(method, annotations.length); + Iterator iter = desc.setters.iterator(); + while(iter.hasNext()) { + if (method.equals(iter.next().method)) { + iter.remove(); + } + } if (JsonWrapperType.BINDING.equals(jsonWrapper.value())) { WrapperDescriptor wrapper = new WrapperDescriptor(); wrapper.method = method; diff --git a/src/main/java/com/jsoniter/spi/JsoniterSpi.java b/src/main/java/com/jsoniter/spi/JsoniterSpi.java index 87e81d30..4b40e77e 100644 --- a/src/main/java/com/jsoniter/spi/JsoniterSpi.java +++ b/src/main/java/com/jsoniter/spi/JsoniterSpi.java @@ -12,8 +12,8 @@ public class JsoniterSpi { private static Config defaultConfig; private static List extensions = new ArrayList(); private static Map typeImpls = new HashMap(); - private static Map globalMapKeyDecoders = new HashMap(); - private static Map globalMapKeyEncoders = new HashMap(); + private static Map globalMapKeyDecoders = new HashMap(); + private static Map globalMapKeyEncoders = new HashMap(); private static Map globalTypeDecoders = new HashMap(); private static Map globalTypeEncoders = new HashMap(); private static Map globalPropertyDecoders = new HashMap(); @@ -27,8 +27,8 @@ protected Config initialValue() { } }; private static volatile Map configNames = new HashMap(); - private static volatile Map mapKeyEncoders = new HashMap(); - private static volatile Map mapKeyDecoders = new HashMap(); + private static volatile Map mapKeyEncoders = new HashMap(); + private static volatile Map mapKeyDecoders = new HashMap(); private static volatile Map encoders = new HashMap(); private static volatile Map decoders = new HashMap(); private static volatile Map objectFactories = new HashMap(); @@ -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); } @@ -98,12 +99,12 @@ public static List getExtensions() { return combined; } - public static void registerMapKeyDecoder(Type mapKeyType, MapKeyDecoder mapKeyDecoder) { + public static void registerMapKeyDecoder(Type mapKeyType, Decoder mapKeyDecoder) { globalMapKeyDecoders.put(mapKeyType, mapKeyDecoder); copyGlobalMapKeyDecoder(getCurrentConfig().configName(), mapKeyType, mapKeyDecoder); } - public static void registerMapKeyEncoder(Type mapKeyType, MapKeyEncoder mapKeyEncoder) { + public static void registerMapKeyEncoder(Type mapKeyType, Encoder mapKeyEncoder) { globalMapKeyEncoders.put(mapKeyType, mapKeyEncoder); copyGlobalMapKeyEncoder(getCurrentConfig().configName(), mapKeyType, mapKeyEncoder); } @@ -159,10 +160,10 @@ public static void registerPropertyEncoder(TypeLiteral typeLiteral, String prope // === copy from global to current === private static void copyGlobalSettings(String configName) { - for (Map.Entry entry : globalMapKeyDecoders.entrySet()) { + for (Map.Entry entry : globalMapKeyDecoders.entrySet()) { copyGlobalMapKeyDecoder(configName, entry.getKey(), entry.getValue()); } - for (Map.Entry entry : globalMapKeyEncoders.entrySet()) { + for (Map.Entry entry : globalMapKeyEncoders.entrySet()) { copyGlobalMapKeyEncoder(configName, entry.getKey(), entry.getValue()); } for (Map.Entry entry : globalTypeDecoders.entrySet()) { @@ -196,11 +197,11 @@ private static void copyGlobalTypeDecoder(String configName, Type type, Decoder addNewDecoder(TypeLiteral.create(type).getDecoderCacheKey(configName), typeDecoder); } - private static void copyGlobalMapKeyDecoder(String configName, Type mapKeyType, MapKeyDecoder mapKeyDecoder) { + private static void copyGlobalMapKeyDecoder(String configName, Type mapKeyType, Decoder mapKeyDecoder) { addNewMapDecoder(TypeLiteral.create(mapKeyType).getDecoderCacheKey(configName), mapKeyDecoder); } - private static void copyGlobalMapKeyEncoder(String configName, Type mapKeyType, MapKeyEncoder mapKeyEncoder) { + private static void copyGlobalMapKeyEncoder(String configName, Type mapKeyType, Encoder mapKeyEncoder) { addNewMapEncoder(TypeLiteral.create(mapKeyType).getEncoderCacheKey(configName), mapKeyEncoder); } @@ -216,23 +217,23 @@ public static String getMapKeyDecoderCacheKey(Type mapKeyType) { // === current === - public synchronized static void addNewMapDecoder(String cacheKey, MapKeyDecoder mapKeyDecoder) { - HashMap newCache = new HashMap(mapKeyDecoders); + public synchronized static void addNewMapDecoder(String cacheKey, Decoder mapKeyDecoder) { + HashMap newCache = new HashMap(mapKeyDecoders); newCache.put(cacheKey, mapKeyDecoder); mapKeyDecoders = newCache; } - public static MapKeyDecoder getMapKeyDecoder(String cacheKey) { + public static Decoder getMapKeyDecoder(String cacheKey) { return mapKeyDecoders.get(cacheKey); } - public synchronized static void addNewMapEncoder(String cacheKey, MapKeyEncoder mapKeyEncoder) { - HashMap newCache = new HashMap(mapKeyEncoders); + public synchronized static void addNewMapEncoder(String cacheKey, Encoder mapKeyEncoder) { + HashMap newCache = new HashMap(mapKeyEncoders); newCache.put(cacheKey, mapKeyEncoder); mapKeyEncoders = newCache; } - public static MapKeyEncoder getMapKeyEncoder(String cacheKey) { + public static Encoder getMapKeyEncoder(String cacheKey) { return mapKeyEncoders.get(cacheKey); } diff --git a/src/main/java/com/jsoniter/spi/MapKeyDecoder.java b/src/main/java/com/jsoniter/spi/MapKeyDecoder.java deleted file mode 100644 index f27de51f..00000000 --- a/src/main/java/com/jsoniter/spi/MapKeyDecoder.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.jsoniter.spi; - -public interface MapKeyDecoder { - Object decode(Slice encodedMapKey); -} diff --git a/src/main/java/com/jsoniter/spi/MapKeyEncoder.java b/src/main/java/com/jsoniter/spi/MapKeyEncoder.java deleted file mode 100644 index 9b0a228c..00000000 --- a/src/main/java/com/jsoniter/spi/MapKeyEncoder.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.jsoniter.spi; - -public interface MapKeyEncoder { - String encode(Object mapKey); -} diff --git a/src/test/java/com/jsoniter/BenchGson.java b/src/test/java/com/jsoniter/BenchGson.java index b4876158..67747b6c 100644 --- a/src/test/java/com/jsoniter/BenchGson.java +++ b/src/test/java/com/jsoniter/BenchGson.java @@ -8,7 +8,6 @@ import com.jsoniter.extra.GsonCompatibilityMode; import com.jsoniter.spi.DecodingMode; import com.jsoniter.spi.JsoniterSpi; -import org.junit.Test; import org.openjdk.jmh.Main; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.BenchmarkParams; @@ -42,7 +41,7 @@ public void benchSetup(BenchmarkParams params) { @Benchmark public void gsonDecoder(Blackhole bh) throws IOException { - FileInputStream stream = new FileInputStream("/tmp/tweets.json"); + FileInputStream stream = new FileInputStream("./src/test/tweets.json"); InputStreamReader reader = new InputStreamReader(stream); try { bh.consume(gson.fromJson(reader, new TypeReference>() { @@ -55,7 +54,7 @@ public void gsonDecoder(Blackhole bh) throws IOException { @Benchmark public void jsoniterReflectionDecoder(Blackhole bh) throws IOException { - FileInputStream stream = new FileInputStream("/tmp/tweets.json"); + FileInputStream stream = new FileInputStream("./src/test/tweets.json"); JsonIterator iter = JsonIteratorPool.borrowJsonIterator(); try { iter.reset(stream); diff --git a/src/test/java/com/jsoniter/IterImplForStreamingTest.java b/src/test/java/com/jsoniter/IterImplForStreamingTest.java index 4efee7e3..e0432d39 100644 --- a/src/test/java/com/jsoniter/IterImplForStreamingTest.java +++ b/src/test/java/com/jsoniter/IterImplForStreamingTest.java @@ -1,13 +1,93 @@ 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 { public void testReadMaxDouble() throws Exception { String maxDouble = "1.7976931348623157e+308"; JsonIterator iter = JsonIterator.parse("1.7976931348623157e+308"); - String number = IterImplForStreaming.readNumber(iter); + IterImplForStreaming.numberChars numberChars = IterImplForStreaming.readNumber(iter); + 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/TestAnnotation.java b/src/test/java/com/jsoniter/TestAnnotation.java index 51838420..3992e3f7 100644 --- a/src/test/java/com/jsoniter/TestAnnotation.java +++ b/src/test/java/com/jsoniter/TestAnnotation.java @@ -14,21 +14,6 @@ public class TestAnnotation extends TestCase { // JsonIterator.setMode(DecodingMode.REFLECTION_MODE); } - public static class TestObject2 { - private int field1; - - @JsonCreator - public TestObject2(@JsonProperty("field1") int field1) { - this.field1 = field1; - } - } - - public void test_ctor() throws IOException { - JsonIterator iter = JsonIterator.parse("{'field1': 100}".replace('\'', '"')); - TestObject2 obj = iter.read(TestObject2.class); - assertEquals(100, obj.field1); - } - public static class TestObject4 { private int field1; diff --git a/src/test/java/com/jsoniter/TestAnnotationJsonCreator.java b/src/test/java/com/jsoniter/TestAnnotationJsonCreator.java new file mode 100644 index 00000000..767b5b04 --- /dev/null +++ b/src/test/java/com/jsoniter/TestAnnotationJsonCreator.java @@ -0,0 +1,54 @@ +package com.jsoniter; + +import com.jsoniter.annotation.JsonCreator; +import com.jsoniter.annotation.JsonIgnore; +import com.jsoniter.annotation.JsonProperty; +import com.jsoniter.annotation.JsonWrapper; +import com.jsoniter.any.Any; +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.Properties; + +public class TestAnnotationJsonCreator extends TestCase { + + + public static class TestObject2 { + private int field1; + + @JsonCreator + public TestObject2(@JsonProperty("field1") int field1) { + this.field1 = field1; + } + } + + public void test_ctor() throws IOException { + JsonIterator iter = JsonIterator.parse("{'field1': 100}".replace('\'', '"')); + TestObject2 obj = iter.read(TestObject2.class); + assertEquals(100, obj.field1); + } + + public static class TestObject { + + @JsonIgnore + private final String id; + @JsonIgnore + private final Properties properties; + + @JsonCreator + public TestObject(@JsonProperty("name") final String name) { + this.id = name; + properties = new Properties(); + } + + @JsonWrapper + public void setProperties(@JsonProperty("props") final Any props) { + // Set props + } + } + + public void test_ctor_and_setter_binding() throws IOException { + JsonIterator iter = JsonIterator.parse("{\"name\": \"test\", \"props\": {\"val\": \"42\"}}"); + iter.read(TestObject.class); + } +} diff --git a/src/test/java/com/jsoniter/TestArray.java b/src/test/java/com/jsoniter/TestArray.java index d2738553..92ea6cf4 100644 --- a/src/test/java/com/jsoniter/TestArray.java +++ b/src/test/java/com/jsoniter/TestArray.java @@ -46,7 +46,7 @@ public void test_one_element() throws IOException { }); assertEquals(Arrays.asList(1), list); iter.reset(iter.buf); - assertArrayEquals(new Object[]{1.0}, iter.read(Object[].class)); + assertArrayEquals(new Object[]{1}, iter.read(Object[].class)); iter.reset(iter.buf); assertEquals(1, iter.read(Any[].class)[0].toInt()); iter.reset(iter.buf); @@ -78,13 +78,13 @@ public void test_two_elements() throws IOException { }); assertEquals(Arrays.asList(1, 2), list); iter.reset(iter.buf); - assertArrayEquals(new Object[]{1.0, 2.0}, iter.read(Object[].class)); + assertArrayEquals(new Object[]{1, 2}, iter.read(Object[].class)); iter.reset(iter.buf); assertEquals(1, iter.read(Any[].class)[0].toInt()); iter.reset(iter.buf); assertEquals(1, iter.readAny().toInt(0)); iter = JsonIterator.parse(" [ 1 , null, 2 ] "); - assertEquals(Arrays.asList(1.0D, null, 2.0D), iter.read()); + assertEquals(Arrays.asList(1, null, 2), iter.read()); } public void test_three_elements() throws IOException { @@ -104,7 +104,7 @@ public void test_three_elements() throws IOException { }); assertEquals(Arrays.asList(1, 2, 3), list); iter.reset(iter.buf); - assertArrayEquals(new Object[]{1.0, 2.0, 3.0}, iter.read(Object[].class)); + assertArrayEquals(new Object[]{1, 2, 3}, iter.read(Object[].class)); iter.reset(iter.buf); assertEquals(1, iter.read(Any[].class)[0].toInt()); iter.reset(iter.buf); @@ -130,7 +130,7 @@ public void test_four_elements() throws IOException { }); assertEquals(Arrays.asList(1, 2, 3, 4), list); iter.reset(iter.buf); - assertArrayEquals(new Object[]{1.0, 2.0, 3.0, 4.0}, iter.read(Object[].class)); + assertArrayEquals(new Object[]{1, 2, 3, 4}, iter.read(Object[].class)); iter.reset(iter.buf); assertEquals(1, iter.read(Any[].class)[0].toInt()); iter.reset(iter.buf); @@ -158,7 +158,7 @@ public void test_five_elements() throws IOException { }); assertEquals(Arrays.asList(1, 2, 3, 4, 5), list); iter.reset(iter.buf); - assertArrayEquals(new Object[]{1.0, 2.0, 3.0, 4.0, 5.0}, iter.read(Object[].class)); + assertArrayEquals(new Object[]{1, 2, 3, 4, 5}, iter.read(Object[].class)); iter.reset(iter.buf); assertEquals(1, iter.read(Any[].class)[0].toInt()); iter.reset(iter.buf); diff --git a/src/test/java/com/jsoniter/TestFloat.java b/src/test/java/com/jsoniter/TestFloat.java index dc9cbc1d..5fc0f851 100644 --- a/src/test/java/com/jsoniter/TestFloat.java +++ b/src/test/java/com/jsoniter/TestFloat.java @@ -5,6 +5,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.math.BigDecimal; public class TestFloat extends TestCase { @@ -76,4 +77,24 @@ private double parseDouble(String input) throws IOException { return JsonIterator.parse(input).readDouble(); } } + + public void testBigDecimal() { + BigDecimal number = JsonIterator.deserialize("100.1", BigDecimal.class); + assertEquals(new BigDecimal("100.1"), number); + } + + public void testChooseDouble() { + Object number = JsonIterator.deserialize("1.1", Object.class); + assertEquals(1.1, number); + 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/TestGenerics.java b/src/test/java/com/jsoniter/TestGenerics.java index a48ea648..446dcc7f 100644 --- a/src/test/java/com/jsoniter/TestGenerics.java +++ b/src/test/java/com/jsoniter/TestGenerics.java @@ -138,6 +138,6 @@ public static class TestObject7 { public void test_wildcard() throws IOException { TestObject7 obj = JsonIterator.deserialize("{\"field\":[1]}", TestObject7.class); - assertEquals(Double.valueOf(1), obj.field.get(0)); + assertEquals(1, obj.field.get(0)); } } diff --git a/src/test/java/com/jsoniter/TestInteger.java b/src/test/java/com/jsoniter/TestInteger.java index 02324b21..589dae02 100644 --- a/src/test/java/com/jsoniter/TestInteger.java +++ b/src/test/java/com/jsoniter/TestInteger.java @@ -6,6 +6,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; //import java.math.BigDecimal; //import java.math.BigInteger; @@ -192,4 +194,19 @@ private long parseLong(String input) throws IOException { return v; } } + + public void testBigInteger() { + BigInteger number = JsonIterator.deserialize("100", BigInteger.class); + assertEquals(new BigInteger("100"), number); + } + + public void testChooseInteger() { + Object number = JsonIterator.deserialize("100", Object.class); + assertEquals(100, number); + } + + public void testChooseLong() { + Object number = JsonIterator.deserialize(Long.valueOf(Long.MAX_VALUE).toString(), Object.class); + assertEquals(Long.MAX_VALUE, number); + } } diff --git a/src/test/java/com/jsoniter/TestMap.java b/src/test/java/com/jsoniter/TestMap.java index ebd0b6bf..487b646d 100644 --- a/src/test/java/com/jsoniter/TestMap.java +++ b/src/test/java/com/jsoniter/TestMap.java @@ -1,7 +1,9 @@ package com.jsoniter; import com.jsoniter.extra.GsonCompatibilityMode; -import com.jsoniter.spi.*; +import com.jsoniter.spi.Decoder; +import com.jsoniter.spi.JsoniterSpi; +import com.jsoniter.spi.TypeLiteral; import junit.framework.TestCase; import java.io.IOException; @@ -39,17 +41,28 @@ 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; } public void test_MapKeyCodec() { - JsoniterSpi.registerMapKeyDecoder(TestObject1.class, new MapKeyDecoder() { + JsoniterSpi.registerMapKeyDecoder(TestObject1.class, new Decoder() { @Override - public Object decode(Slice encodedMapKey) { - Integer field = Integer.valueOf(encodedMapKey.toString()); + public Object decode(JsonIterator iter) throws IOException { TestObject1 obj = new TestObject1(); - obj.Field = field; + obj.Field = Integer.valueOf(iter.readString()); return obj; } }); @@ -67,5 +80,4 @@ public Object decode(Slice encodedMapKey) { assertEquals(1, keys.size()); assertEquals(100, keys.get(0).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/TestArray.java b/src/test/java/com/jsoniter/any/TestArray.java index 2779cb2f..9744fec8 100644 --- a/src/test/java/com/jsoniter/any/TestArray.java +++ b/src/test/java/com/jsoniter/any/TestArray.java @@ -72,4 +72,9 @@ public void test_equals_and_hashcode() { assertEquals(obj1, obj2); assertEquals(obj1.hashCode(), obj2.hashCode()); } + + public void test_null() { + Any x = JsonIterator.deserialize("{\"test\":null}"); + assertFalse(x.get("test").iterator().hasNext()); + } } 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 new file mode 100644 index 00000000..faf72d31 --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestFloat.java @@ -0,0 +1,17 @@ +package com.jsoniter.output; + +import junit.framework.TestCase; + +import java.math.BigDecimal; + +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/TestGenerics.java b/src/test/java/com/jsoniter/output/TestGenerics.java index 53d5a95a..ff06d780 100644 --- a/src/test/java/com/jsoniter/output/TestGenerics.java +++ b/src/test/java/com/jsoniter/output/TestGenerics.java @@ -41,7 +41,7 @@ public void test_inherited_getter_is_not_duplicate() throws IOException { public static class TestObject7 { public List field; - public Map field2; + public Map field2; } public void test_wildcard() throws IOException { diff --git a/src/test/java/com/jsoniter/output/TestInteger.java b/src/test/java/com/jsoniter/output/TestInteger.java new file mode 100644 index 00000000..64dc1ce9 --- /dev/null +++ b/src/test/java/com/jsoniter/output/TestInteger.java @@ -0,0 +1,11 @@ +package com.jsoniter.output; + +import junit.framework.TestCase; + +import java.math.BigInteger; + +public class TestInteger extends TestCase { + public void testBigInteger() { + assertEquals("100", JsonStream.serialize(new BigInteger("100"))); + } +} diff --git a/src/test/java/com/jsoniter/output/TestMap.java b/src/test/java/com/jsoniter/output/TestMap.java index 1cc0b0dc..2b91d6f0 100644 --- a/src/test/java/com/jsoniter/output/TestMap.java +++ b/src/test/java/com/jsoniter/output/TestMap.java @@ -1,13 +1,14 @@ package com.jsoniter.output; import com.jsoniter.spi.Config; +import com.jsoniter.spi.Encoder; import com.jsoniter.spi.JsoniterSpi; -import com.jsoniter.spi.MapKeyEncoder; import com.jsoniter.spi.TypeLiteral; import junit.framework.TestCase; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; @@ -65,16 +66,29 @@ 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; } public void test_MapKeyCodec() { - JsoniterSpi.registerMapKeyEncoder(TestObject1.class, new MapKeyEncoder() { + JsoniterSpi.registerMapKeyEncoder(TestObject1.class, new Encoder() { @Override - public String encode(Object mapKey) { - TestObject1 obj = (TestObject1) mapKey; - return String.valueOf(obj.Field); + public void encode(Object obj, JsonStream stream) throws IOException { + TestObject1 mapKey = (TestObject1) obj; + stream.writeVal(String.valueOf(mapKey.Field)); } }); HashMap obj = new HashMap(); @@ -120,4 +134,26 @@ public void test_indention_with_empty_map() { .build(); assertEquals("{}", JsonStream.serialize(config, new HashMap())); } + + public void test_int_as_map_key() { + HashMap m = new HashMap(); + m.put(1, "2"); + assertEquals("{\"1\":\"2\"}", JsonStream.serialize(new TypeLiteral>() { + }, m)); + } + + public void test_object_key() { + HashMap m = new HashMap(); + 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 fc33cede..d3196ed4 100644 --- a/src/test/java/com/jsoniter/suite/AllTestCases.java +++ b/src/test/java/com/jsoniter/suite/AllTestCases.java @@ -1,13 +1,16 @@ package com.jsoniter.suite; import com.jsoniter.*; +import com.jsoniter.TestFloat; import com.jsoniter.TestGenerics; import com.jsoniter.TestGson; import com.jsoniter.TestNested; 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; import org.junit.runners.Suite; @@ -20,6 +23,7 @@ TestAnnotationJsonWrapper.class, TestAnnotationJsonUnwrapper.class, TestAnnotation.class, + TestAnnotationJsonCreator.class, com.jsoniter.output.TestGenerics.class, TestCustomizeType.class, TestDemo.class, TestExisting.class, TestGenerics.class, TestGenerics.class, TestIO.class, @@ -39,8 +43,8 @@ com.jsoniter.TestMap.class, com.jsoniter.output.TestMap.class, TestNative.class, - TestBoolean.class, TestFloat.class, - TestList.class, + TestBoolean.class, TestFloat.class, com.jsoniter.output.TestFloat.class, + TestList.class, TestInteger.class, com.jsoniter.output.TestInteger.class, com.jsoniter.output.TestJackson.class, com.jsoniter.TestJackson.class, TestSpiTypeEncoder.class, @@ -49,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 { }