Skip to content

Commit 0467788

Browse files
committed
decode enum efficiently
1 parent a17adc2 commit 0467788

9 files changed

Lines changed: 149 additions & 22 deletions

File tree

src/main/java/com/jsoniter/Codegen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ private static String genSource(Class clazz, Type[] typeArgs) {
215215
return CodegenImplArray.genCollection(clazz, typeArgs);
216216
}
217217
if (clazz.isEnum()) {
218-
return CodegenImplNative.genEnum(clazz);
218+
return CodegenImplEnum.genEnum(clazz);
219219
}
220220
ClassDescriptor desc = JsoniterSpi.getDecodingClassDescriptor(clazz, false);
221221
if (shouldUseStrictMode(desc)) {

src/main/java/com/jsoniter/CodegenAccess.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ public static final Slice readObjectFieldAsSlice(JsonIterator iter) throws IOExc
156156
return IterImpl.readObjectFieldAsSlice(iter);
157157
}
158158

159+
public static final Slice readSlice(JsonIterator iter) throws IOException {
160+
return IterImpl.readSlice(iter);
161+
}
162+
159163
final static boolean skipWhitespacesWithoutLoadMore(JsonIterator iter) throws IOException {
160164
for (int i = iter.head; i < iter.tail; i++) {
161165
byte c = iter.buf[i];
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.jsoniter;
2+
3+
import java.util.*;
4+
5+
public class CodegenImplEnum {
6+
public static String genEnum(Class clazz) {
7+
StringBuilder lines = new StringBuilder();
8+
append(lines, "if (iter.readNull()) { return null; }");
9+
append(lines, "com.jsoniter.Slice field = com.jsoniter.CodegenAccess.readSlice(iter);");
10+
append(lines, "switch (field.len()) {");
11+
append(lines, renderTriTree(buildTriTree(Arrays.asList(clazz.getEnumConstants()))));
12+
append(lines, "}"); // end of switch
13+
append(lines, String.format("throw iter.reportError(\"decode enum\", field + \" is not valid enum for %s\");", clazz.getName()));
14+
return lines.toString();
15+
}
16+
17+
private static Map<Integer, Object> buildTriTree(List<Object> allConsts) {
18+
Map<Integer, Object> trieTree = new HashMap<Integer, Object>();
19+
for (Object e : allConsts) {
20+
byte[] fromNameBytes = e.toString().getBytes();
21+
Map<Byte, Object> current = (Map<Byte, Object>) trieTree.get(fromNameBytes.length);
22+
if (current == null) {
23+
current = new HashMap<Byte, Object>();
24+
trieTree.put(fromNameBytes.length, current);
25+
}
26+
for (int i = 0; i < fromNameBytes.length - 1; i++) {
27+
byte b = fromNameBytes[i];
28+
Map<Byte, Object> next = (Map<Byte, Object>) current.get(b);
29+
if (next == null) {
30+
next = new HashMap<Byte, Object>();
31+
current.put(b, next);
32+
}
33+
current = next;
34+
}
35+
current.put(fromNameBytes[fromNameBytes.length - 1], e);
36+
}
37+
return trieTree;
38+
}
39+
40+
private static String renderTriTree(Map<Integer, Object> trieTree) {
41+
StringBuilder switchBody = new StringBuilder();
42+
for (Map.Entry<Integer, Object> entry : trieTree.entrySet()) {
43+
Integer len = entry.getKey();
44+
append(switchBody, "case " + len + ": ");
45+
Map<Byte, Object> current = (Map<Byte, Object>) entry.getValue();
46+
addFieldDispatch(switchBody, len, 0, current, new ArrayList<Byte>());
47+
append(switchBody, "break;");
48+
}
49+
return switchBody.toString();
50+
}
51+
52+
private static void addFieldDispatch(
53+
StringBuilder lines, int len, int i, Map<Byte, Object> current, List<Byte> bytesToCompare) {
54+
for (Map.Entry<Byte, Object> entry : current.entrySet()) {
55+
Byte b = entry.getKey();
56+
if (i == len - 1) {
57+
append(lines, "if (");
58+
for (int j = 0; j < bytesToCompare.size(); j++) {
59+
Byte a = bytesToCompare.get(j);
60+
append(lines, String.format("field.at(%d)==%s && ", i - bytesToCompare.size() + j, a));
61+
}
62+
append(lines, String.format("field.at(%d)==%s", i, b));
63+
append(lines, ") {");
64+
Object e = entry.getValue();
65+
append(lines, String.format("return %s.%s;", e.getClass().getName(), e.toString()));
66+
append(lines, "}");
67+
continue;
68+
}
69+
Map<Byte, Object> next = (Map<Byte, Object>) entry.getValue();
70+
if (next.size() == 1) {
71+
ArrayList<Byte> nextBytesToCompare = new ArrayList<Byte>(bytesToCompare);
72+
nextBytesToCompare.add(b);
73+
addFieldDispatch(lines, len, i + 1, next, nextBytesToCompare);
74+
continue;
75+
}
76+
append(lines, "if (");
77+
for (int j = 0; j < bytesToCompare.size(); j++) {
78+
Byte a = bytesToCompare.get(j);
79+
append(lines, String.format("field.at(%d)==%s && ", i - bytesToCompare.size() + j, a));
80+
}
81+
append(lines, String.format("field.at(%d)==%s", i, b));
82+
append(lines, ") {");
83+
addFieldDispatch(lines, len, i + 1, next, new ArrayList<Byte>());
84+
append(lines, "}");
85+
}
86+
}
87+
88+
private static void append(StringBuilder lines, String str) {
89+
lines.append(str);
90+
lines.append("\n");
91+
}
92+
}

src/main/java/com/jsoniter/CodegenImplNative.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,6 @@ public static String genNative(String nativeReadKey) {
6060
return "return " + op + ";";
6161
}
6262

63-
public static String genEnum(Class clazz) {
64-
// TODO: avoid create string
65-
StringBuilder lines = new StringBuilder();
66-
append(lines, "return {{clazz}}.valueOf(iter.readString());");
67-
return lines.toString().replace("{{clazz}}", clazz.getName());
68-
}
69-
7063
public static String genReadOp(Type type) {
7164
if (type instanceof Class) {
7265
Class clazz = (Class) type;
@@ -97,9 +90,4 @@ public static String getTypeName(Type fieldType) {
9790
throw new JsonException("unsupported type: " + fieldType);
9891
}
9992
}
100-
101-
private static void append(StringBuilder lines, String str) {
102-
lines.append(str);
103-
lines.append("\n");
104-
}
10593
}

src/main/java/com/jsoniter/IterImpl.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ public static final int readObjectFieldAsHash(JsonIterator iter) throws IOExcept
2727
}
2828

2929
public static final Slice readObjectFieldAsSlice(JsonIterator iter) throws IOException {
30-
if (nextToken(iter) != '"') {
31-
throw iter.reportError("readObjectFieldAsSlice", "expect \"");
32-
}
3330
Slice field = readSlice(iter);
3431
if (nextToken(iter) != ':') {
3532
throw iter.reportError("readObjectFieldAsSlice", "expect : after object field");
@@ -129,7 +126,10 @@ final static boolean skipNumber(JsonIterator iter) throws IOException {
129126
}
130127

131128
// read the bytes between " "
132-
final static Slice readSlice(JsonIterator iter) throws IOException {
129+
public final static Slice readSlice(JsonIterator iter) throws IOException {
130+
if (IterImpl.nextToken(iter) != '"') {
131+
throw iter.reportError("readSlice", "expect \" for string");
132+
}
133133
int end = IterImplString.findSliceEnd(iter);
134134
if (end == -1) {
135135
throw iter.reportError("readSlice", "incomplete string");

src/main/java/com/jsoniter/IterImplString.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,6 @@ private static char lowSurrogate(int codePoint) {
158158

159159
public static final byte[] readBase64(JsonIterator iter) throws IOException {
160160
// from https://gist.github.com/EmilHernvall/953733
161-
if (IterImpl.nextToken(iter) != '"') {
162-
throw iter.reportError("readBase64", "expect \" for base64");
163-
}
164161
Slice slice = IterImpl.readSlice(iter);
165162
if (slice == null) {
166163
return null;

src/main/java/com/jsoniter/ReflectionDecoderFactory.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ public Object decode(JsonIterator iter) throws IOException {
2929
if (Map.class.isAssignableFrom(clazz)) {
3030
return new ReflectionMapDecoder(clazz, typeArgs);
3131
}
32+
if (clazz.isEnum()) {
33+
return new ReflectionEnumDecoder(clazz);
34+
}
3235
return new ReflectionObjectDecoder(clazz).create();
3336
}
3437
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.jsoniter;
2+
3+
import com.jsoniter.spi.Decoder;
4+
5+
import java.io.IOException;
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
9+
class ReflectionEnumDecoder implements Decoder{
10+
11+
private final Map<Slice, Object> enumMap = new HashMap<Slice, Object>();
12+
private Class clazz;
13+
14+
public ReflectionEnumDecoder(Class clazz) {
15+
this.clazz = clazz;
16+
for (Object e : clazz.getEnumConstants()) {
17+
enumMap.put(Slice.make(e.toString()), e);
18+
}
19+
}
20+
@Override
21+
public Object decode(JsonIterator iter) throws IOException {
22+
if (iter.readNull()) {
23+
return null;
24+
}
25+
Slice slice = IterImpl.readSlice(iter);
26+
Object e = enumMap.get(slice);
27+
if (e == null) {
28+
throw iter.reportError("ReflectionEnumDecoder", slice + " is not valid enum for " + clazz);
29+
}
30+
return e;
31+
}
32+
}

src/test/java/com/jsoniter/TestObject.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
public class TestObject extends TestCase {
1212

1313
static {
14-
// JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH);
14+
JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH);
1515
}
1616

1717
public static class EmptyClass {
@@ -143,13 +143,24 @@ public Object create(Class clazz) {
143143
public static class TestObject5 {
144144

145145
public enum MyEnum {
146-
HELLO
146+
HELLO,
147+
WORLD,
148+
WOW
147149
}
148150
public MyEnum field1;
149151
}
150152

151153
public void test_enum() throws IOException {
152154
TestObject5 obj = JsonIterator.deserialize("{\"field1\":\"HELLO\"}", TestObject5.class);
153155
assertEquals(TestObject5.MyEnum.HELLO, obj.field1);
156+
try {
157+
JsonIterator.deserialize("{\"field1\":\"HELLO1\"}", TestObject5.class);
158+
fail();
159+
} catch (JsonException e) {
160+
}
161+
obj = JsonIterator.deserialize("{\"field1\":null}", TestObject5.class);
162+
assertNull(obj.field1);
163+
obj = JsonIterator.deserialize("{\"field1\":\"WOW\"}", TestObject5.class);
164+
assertEquals(TestObject5.MyEnum.WOW, obj.field1);
154165
}
155166
}

0 commit comments

Comments
 (0)