Skip to content

Commit 77489e4

Browse files
committed
support map
1 parent d1e61cb commit 77489e4

10 files changed

Lines changed: 234 additions & 64 deletions

File tree

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

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ private static String genSource(String cacheKey, Class clazz, Type[] typeArgs) {
7979
return CodegenImplArray.genArray(clazz);
8080
}
8181
if (Map.class.isAssignableFrom(clazz)) {
82-
return genMap(clazz, typeArgs);
82+
return CodegenImplMap.genMap(clazz, typeArgs);
8383
}
8484
if (Collection.class.isAssignableFrom(clazz)) {
8585
return CodegenImplArray.genCollection(clazz, typeArgs);
@@ -94,46 +94,9 @@ private static String genSource(String cacheKey, Class clazz, Type[] typeArgs) {
9494
}
9595
}
9696

97-
private static String genMap(Class clazz, Type[] typeArgs) {
98-
Type keyType = String.class;
99-
Type valueType = Object.class;
100-
if (typeArgs.length == 0) {
101-
// default to Map<String, Object>
102-
} else if (typeArgs.length == 2) {
103-
keyType = typeArgs[0];
104-
valueType = typeArgs[1];
105-
} else {
106-
throw new IllegalArgumentException(
107-
"can not bind to generic collection without argument types, " +
108-
"try syntax like TypeLiteral<Map<String, String>>{}");
109-
}
110-
if (keyType != String.class) {
111-
throw new IllegalArgumentException("map key must be String");
112-
}
113-
if (clazz == Map.class) {
114-
clazz = HashMap.class;
115-
}
116-
StringBuilder lines = new StringBuilder();
117-
append(lines, "public static Object decode_(com.jsoniter.JsonIterator iter) {");
118-
append(lines, "{{clazz}} map = ({{clazz}})com.jsoniter.CodegenAccess.resetExistingObject(iter);");
119-
append(lines, "if (map == null) { map = new {{clazz}}(); }");
120-
append(lines, "for (String field = iter.readObject(); field != null; field = iter.readObject()) {");
121-
append(lines, "map.put(field, {{op}});");
122-
append(lines, "}");
123-
append(lines, "return map;");
124-
append(lines, "}");
125-
return lines.toString().replace("{{clazz}}", clazz.getName()).replace("{{op}}", CodegenImplNative.genReadOp(valueType));
126-
}
127-
12897
public static void addNewDecoder(String cacheKey, Decoder decoder) {
12998
HashMap<String, Decoder> newCache = new HashMap<String, Decoder>(cache);
13099
newCache.put(cacheKey, decoder);
131100
cache = newCache;
132101
}
133-
134-
private static void append(StringBuilder lines, String str) {
135-
lines.append(str);
136-
lines.append("\n");
137-
}
138-
139102
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.jsoniter;
2+
3+
import java.lang.reflect.Type;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
7+
class CodegenImplMap {
8+
9+
public static String genMap(Class clazz, Type[] typeArgs) {
10+
Type keyType = String.class;
11+
Type valueType = Object.class;
12+
if (typeArgs.length == 0) {
13+
// default to Map<String, Object>
14+
} else if (typeArgs.length == 2) {
15+
keyType = typeArgs[0];
16+
valueType = typeArgs[1];
17+
} else {
18+
throw new IllegalArgumentException(
19+
"can not bind to generic collection without argument types, " +
20+
"try syntax like TypeLiteral<Map<String, String>>{}");
21+
}
22+
if (keyType != String.class) {
23+
throw new IllegalArgumentException("map key must be String");
24+
}
25+
if (clazz == Map.class) {
26+
clazz = HashMap.class;
27+
}
28+
StringBuilder lines = new StringBuilder();
29+
append(lines, "public static Object decode_(com.jsoniter.JsonIterator iter) {");
30+
append(lines, "{{clazz}} map = ({{clazz}})com.jsoniter.CodegenAccess.resetExistingObject(iter);");
31+
append(lines, "if (map == null) { map = new {{clazz}}(); }");
32+
append(lines, "for (String field = iter.readObject(); field != null; field = iter.readObject()) {");
33+
append(lines, "map.put(field, {{op}});");
34+
append(lines, "}");
35+
append(lines, "return map;");
36+
append(lines, "}");
37+
return lines.toString().replace("{{clazz}}", clazz.getName()).replace("{{op}}", CodegenImplNative.genReadOp(valueType));
38+
}
39+
40+
private static void append(StringBuilder lines, String str) {
41+
lines.append(str);
42+
lines.append("\n");
43+
}
44+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ private static String genSource(String cacheKey, Class clazz, Type[] typeArgs) {
7979
if (clazz.isArray()) {
8080
return CodegenImplArray.genArray(clazz);
8181
}
82+
if (Map.class.isAssignableFrom(clazz)) {
83+
return CodegenImplMap.genMap(clazz, typeArgs);
84+
}
8285
if (Collection.class.isAssignableFrom(clazz)) {
8386
return CodegenImplArray.genCollection(clazz, typeArgs);
8487
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ public static String genArray(Class clazz) {
1313
}
1414
StringBuilder lines = new StringBuilder();
1515
append(lines, "public static void encode_(Object obj, com.jsoniter.output.JsonStream stream) {");
16-
append(lines, "stream.startArray();");
16+
append(lines, "if (obj == null) { stream.writeNull(); return; }");
1717
append(lines, "{{comp}}[] arr = ({{comp}}[])obj;");
18+
append(lines, "if (arr.length == 0) { stream.writeEmptyArray(); return; }");
19+
append(lines, "stream.startArray();");
1820
append(lines, "for (int i = 0; i < arr.length; i++) {");
1921
append(lines, "stream.writeVal(({{comp}})arr[i]);");
2022
append(lines, "stream.writeMore();");
@@ -51,8 +53,12 @@ public static String genCollection(Class clazz, Type[] typeArgs) {
5153
private static String genCollection(Class clazz, Type compType) {
5254
StringBuilder lines = new StringBuilder();
5355
append(lines, "public static void encode_(Object obj, com.jsoniter.output.JsonStream stream) {");
54-
append(lines, "stream.startArray();");
56+
append(lines, "if (obj == null) { stream.writeNull(); return; }");
5557
append(lines, "java.util.Iterator iter = ((java.util.Collection)obj).iterator();");
58+
append(lines, "if (!iter.hasNext()) { stream.writeEmptyArray(); return; }");
59+
append(lines, "stream.startArray();");
60+
append(lines, "stream.writeVal(({{comp}})iter.next());");
61+
append(lines, "stream.writeMore();");
5662
append(lines, "while (iter.hasNext()) {");
5763
append(lines, "stream.writeVal(({{comp}})iter.next());");
5864
append(lines, "stream.writeMore();");
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.jsoniter.output;
2+
3+
import java.lang.reflect.Type;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
7+
class CodegenImplMap {
8+
public static String genMap(Class clazz, Type[] typeArgs) {
9+
Type keyType = String.class;
10+
Type valueType = Object.class;
11+
if (typeArgs.length == 0) {
12+
// default to Map<String, Object>
13+
} else if (typeArgs.length == 2) {
14+
keyType = typeArgs[0];
15+
valueType = typeArgs[1];
16+
} else {
17+
throw new IllegalArgumentException(
18+
"can not bind to generic collection without argument types, " +
19+
"try syntax like TypeLiteral<Map<String, String>>{}");
20+
}
21+
if (keyType != String.class) {
22+
throw new IllegalArgumentException("map key must be String");
23+
}
24+
if (clazz == Map.class) {
25+
clazz = HashMap.class;
26+
}
27+
StringBuilder lines = new StringBuilder();
28+
append(lines, "public static void encode_(Object obj, com.jsoniter.output.JsonStream stream) {");
29+
append(lines, "if (obj == null) { stream.writeNull(); return; }");
30+
append(lines, "java.util.Map map = (java.util.Map)obj;");
31+
append(lines, "java.util.Iterator iter = map.entrySet().iterator();");
32+
append(lines, "if(!iter.hasNext()) { stream.writeEmptyObject(); return; }");
33+
append(lines, "java.util.Map.Entry entry = iter.next();");
34+
append(lines, "stream.startObject();");
35+
append(lines, "stream.writeField((String)entry.getKey());");
36+
append(lines, "stream.writeVal(entry.getValue());");
37+
append(lines, "stream.writeMore();");
38+
append(lines, "while(iter.hasNext()) {");
39+
append(lines, "entry = iter.next();");
40+
append(lines, "stream.writeField((String)entry.getKey());");
41+
append(lines, "stream.writeVal(entry.getValue());");
42+
append(lines, "stream.writeMore();");
43+
append(lines, "}");
44+
append(lines, "stream.endObject();");
45+
append(lines, "}");
46+
return lines.toString().replace("{{clazz}}", clazz.getName());
47+
}
48+
49+
private static void append(StringBuilder lines, String str) {
50+
lines.append(str);
51+
lines.append("\n");
52+
}
53+
}

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,27 @@
66
import java.util.ArrayList;
77
import java.util.List;
88

9-
public class CodegenImplObject {
9+
class CodegenImplObject {
1010
public static String genObject(Class clazz) {
1111
List<Binding> allBindings = new ArrayList<Binding>(ExtensionManager.getFields(clazz));
1212
allBindings.addAll(ExtensionManager.getGetters(clazz));
1313
StringBuilder lines = new StringBuilder();
1414
append(lines, "public static void encode_(Object rawObj, com.jsoniter.output.JsonStream stream) {");
15-
append(lines, "{{clazz}} obj = ({{clazz}})rawObj;");
16-
append(lines, "stream.startObject();");
17-
for (Binding field : allBindings) {
18-
for (String fromName : field.fromNames) {
19-
append(lines, String.format("stream.writeField(\"%s\");", field.name));
20-
append(lines, String.format("stream.writeVal(obj.%s);", fromName));
21-
append(lines, "stream.writeMore();");
15+
append(lines, "if (rawObj == null) { stream.writeNull(); return; }");
16+
if (allBindings.isEmpty()) {
17+
append(lines, "stream.writeEmptyObject();");
18+
} else {
19+
append(lines, "{{clazz}} obj = ({{clazz}})rawObj;");
20+
append(lines, "stream.startObject();");
21+
for (Binding field : allBindings) {
22+
for (String fromName : field.fromNames) {
23+
append(lines, String.format("stream.writeField(\"%s\");", field.name));
24+
append(lines, String.format("stream.writeVal(obj.%s);", fromName));
25+
append(lines, "stream.writeMore();");
26+
}
2227
}
28+
append(lines, "stream.endObject();");
2329
}
24-
append(lines, "stream.endObject();");
2530
append(lines, "}");
2631
return lines.toString().replace("{{clazz}}", clazz.getCanonicalName());
2732
}

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

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private final void flushBuffer() throws IOException {
6969

7070
public final void writeVal(String val) throws IOException {
7171
if (val == null) {
72-
write(NULL, 0, NULL.length);
72+
writeNull();
7373
} else {
7474
write((int) (byte) '"');
7575
flushBuffer();
@@ -86,7 +86,7 @@ public final void writeRaw(String val) throws IOException {
8686
public final void writeVal(Boolean val) throws IOException {
8787
// TODO: convert boolean directly into bytes
8888
if (val == null) {
89-
write(NULL, 0, NULL.length);
89+
writeNull();
9090
} else {
9191
writeRaw(Boolean.toString(val));
9292
}
@@ -100,7 +100,7 @@ public final void writeVal(boolean val) throws IOException {
100100
public final void writeVal(Short val) throws IOException {
101101
// TODO: convert short directly into bytes
102102
if (val == null) {
103-
write(NULL, 0, NULL.length);
103+
writeNull();
104104
} else {
105105
writeRaw(Short.toString(val));
106106
}
@@ -114,7 +114,7 @@ public final void writeVal(short val) throws IOException {
114114
public final void writeVal(Integer val) throws IOException {
115115
// TODO: convert int directly into bytes
116116
if (val == null) {
117-
write(NULL, 0, NULL.length);
117+
writeNull();
118118
} else {
119119
writeRaw(Integer.toString(val));
120120
}
@@ -128,7 +128,7 @@ public final void writeVal(int val) throws IOException {
128128
public final void writeVal(Long val) throws IOException {
129129
// TODO: convert long directly into bytes
130130
if (val == null) {
131-
write(NULL, 0, NULL.length);
131+
writeNull();
132132
} else {
133133
writeRaw(Long.toString(val));
134134
}
@@ -141,7 +141,7 @@ public final void writeVal(long val) throws IOException {
141141

142142
public final void writeVal(Float val) throws IOException {
143143
if (val == null) {
144-
write(NULL, 0, NULL.length);
144+
writeNull();
145145
} else {
146146
writeRaw(Float.toString(val));
147147
}
@@ -153,12 +153,26 @@ public final void writeVal(float val) throws IOException {
153153

154154
public final void writeVal(Double val) throws IOException {
155155
if (val == null) {
156-
write(NULL, 0, NULL.length);
156+
writeNull();
157157
} else {
158158
writeRaw(Double.toString(val));
159159
}
160160
}
161161

162+
public final void writeNull() throws IOException {
163+
write(NULL, 0, NULL.length);
164+
}
165+
166+
public final void writeEmptyObject() throws IOException {
167+
write('{');
168+
write('}');
169+
}
170+
171+
public final void writeEmptyArray() throws IOException {
172+
write('[');
173+
write(']');
174+
}
175+
162176
public final void writeVal(double val) throws IOException {
163177
writeRaw(Double.toString(val));
164178
}

src/test/java/com/jsoniter/output/TestArray.java

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,58 @@
1111
public class TestArray extends TestCase {
1212

1313
private ByteArrayOutputStream baos;
14-
private JsonStream generator;
14+
private JsonStream stream;
1515

1616
public void setUp() {
1717
baos = new ByteArrayOutputStream();
18-
generator = new JsonStream(baos, 4096);
18+
stream = new JsonStream(baos, 4096);
1919
}
2020

2121
public void test_gen_array() throws IOException {
22-
generator.writeVal(new String[] {"hello", "world"});
23-
generator.close();
22+
stream.writeVal(new String[] {"hello", "world"});
23+
stream.close();
2424
assertEquals("['hello','world']".replace('\'', '"'), baos.toString());
2525
}
2626

2727
public void test_collection() throws IOException {
2828
ArrayList list = new ArrayList();
2929
list.add("hello");
3030
list.add("world");
31-
generator.writeVal(new TypeLiteral<List<String>>(){}, list);
32-
generator.close();
31+
stream.writeVal(new TypeLiteral<List<String>>(){}, list);
32+
stream.close();
3333
assertEquals("['hello','world']".replace('\'', '"'), baos.toString());
3434
}
3535

3636
public void test_collection_without_type() throws IOException {
3737
ArrayList list = new ArrayList();
3838
list.add("hello");
3939
list.add("world");
40-
generator.writeVal(list);
41-
generator.close();
40+
stream.writeVal(list);
41+
stream.close();
4242
assertEquals("['hello','world']".replace('\'', '"'), baos.toString());
4343
}
44+
45+
public void test_empty_array() throws IOException {
46+
stream.writeVal(new String[0]);
47+
stream.close();
48+
assertEquals("[]".replace('\'', '"'), baos.toString());
49+
}
50+
51+
public void test_null_array() throws IOException {
52+
stream.writeVal(new TypeLiteral<String[]>(){}, null);
53+
stream.close();
54+
assertEquals("null".replace('\'', '"'), baos.toString());
55+
}
56+
57+
public void test_empty_collection() throws IOException {
58+
stream.writeVal(new ArrayList());
59+
stream.close();
60+
assertEquals("[]".replace('\'', '"'), baos.toString());
61+
}
62+
63+
public void test_null_collection() throws IOException {
64+
stream.writeVal(new TypeLiteral<ArrayList>(){}, null);
65+
stream.close();
66+
assertEquals("null".replace('\'', '"'), baos.toString());
67+
}
4468
}

0 commit comments

Comments
 (0)