Skip to content

Commit a0e7e1a

Browse files
committed
FIX: When a object has empty list, JSON writes invalid format.
1 parent 0a53ded commit a0e7e1a

File tree

2 files changed

+178
-159
lines changed

2 files changed

+178
-159
lines changed

src/main/java/kiss/JSON.java

Lines changed: 160 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,158 +1,160 @@
1-
/*
2-
* Copyright (C) 2016 Nameless Production Committee
3-
*
4-
* Licensed under the MIT License (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://opensource.org/licenses/mit-license.php
9-
*/
10-
package kiss;
11-
12-
import java.io.IOException;
13-
import java.util.List;
14-
import java.util.concurrent.ConcurrentHashMap;
15-
16-
import kiss.model.Model;
17-
import kiss.model.Property;
18-
import kiss.model.PropertyWalker;
19-
20-
/**
21-
* <p>
22-
* JSON serializer for Java object graph. This serializer rejects cyclic node within ancestor nodes,
23-
* but same object in sibling nodes will be acceptable.
24-
* </p>
25-
*
26-
* @version 2010/01/12 20:32:11
27-
*/
28-
class JSON implements PropertyWalker {
29-
30-
/** The record for traversed objects. */
31-
private final ConcurrentHashMap reference = new ConcurrentHashMap();
32-
33-
/** The charcter sequence for output as JSON. */
34-
private final Appendable out;
35-
36-
/** The flag whether the current property is the first item in context or not. */
37-
private boolean first = true;
38-
39-
/**
40-
* JSON serializer.
41-
*
42-
* @param out An output target.
43-
*/
44-
JSON(Appendable out) {
45-
this.out = out;
46-
}
47-
48-
/**
49-
* {@inheritDoc}
50-
*/
51-
@Override
52-
public void walk(Model model, Property property, Object node) {
53-
if (!property.isTransient) {
54-
try {
55-
// ========================================
56-
// Enter Node
57-
// ========================================
58-
// check whether this is first property in current context or not.
59-
if (first) {
60-
// mark as not first
61-
first = false;
62-
} else {
63-
// write property seperator
64-
out.append(',');
65-
}
66-
67-
// write property key (root node and List node doesn't need key)
68-
if (reference.size() != 0 && model.type != List.class) {
69-
write(property.name);
70-
out.append(':');
71-
}
72-
73-
// write property value
74-
if (property.isAttribute()) {
75-
write(I.transform(node, String.class));
76-
} else {
77-
// check cyclic node (non-attribute node only apply this check)
78-
if (reference.putIfAbsent(node, 0) != null) {
79-
throw new ClassCircularityError(reference.toString());
80-
} else {
81-
// write suitable brace
82-
out.append(property.model.type == List.class ? '[' : '{');
83-
84-
// reset next context
85-
first = true;
86-
}
87-
88-
// ========================================
89-
// Traverse Child Node
90-
// ========================================
91-
property.model.walk(node, this);
92-
93-
// ========================================
94-
// Leave Node
95-
// ========================================
96-
// unregister non-attribute node
97-
reference.remove(node);
98-
99-
// write suitable brace
100-
out.append(property.model.type == List.class ? ']' : '}');
101-
}
102-
} catch (IOException e) {
103-
throw I.quiet(e);
104-
}
105-
}
106-
}
107-
108-
/**
109-
* <p>
110-
* Write JSON literal with quote.
111-
* </p>
112-
*
113-
* @param value A character sequence.
114-
* @throws IOException
115-
*/
116-
private void write(String value) throws IOException {
117-
out.append('"');
118-
119-
for (int i = 0; i < value.length(); i++) {
120-
char c = value.charAt(i);
121-
122-
switch (c) {
123-
case '"':
124-
out.append("\\\"");
125-
break;
126-
127-
case '\\':
128-
out.append("\\\\");
129-
break;
130-
131-
case '\b':
132-
out.append("\\b");
133-
break;
134-
135-
case '\f':
136-
out.append("\\f");
137-
break;
138-
139-
case '\n':
140-
out.append("\\n");
141-
break;
142-
143-
case '\r':
144-
out.append("\\r");
145-
break;
146-
147-
case '\t':
148-
out.append("\\t");
149-
break;
150-
151-
default:
152-
out.append(c);
153-
}
154-
}
155-
156-
out.append('"');
157-
}
158-
}
1+
/*
2+
* Copyright (C) 2016 Nameless Production Committee
3+
*
4+
* Licensed under the MIT License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://opensource.org/licenses/mit-license.php
9+
*/
10+
package kiss;
11+
12+
import java.io.IOException;
13+
import java.util.List;
14+
import java.util.concurrent.ConcurrentHashMap;
15+
16+
import kiss.model.Model;
17+
import kiss.model.Property;
18+
import kiss.model.PropertyWalker;
19+
20+
/**
21+
* <p>
22+
* JSON serializer for Java object graph. This serializer rejects cyclic node within ancestor nodes,
23+
* but same object in sibling nodes will be acceptable.
24+
* </p>
25+
*
26+
* @version 2016/03/15 18:10:13
27+
*/
28+
class JSON implements PropertyWalker {
29+
30+
/** The record for traversed objects. */
31+
private final ConcurrentHashMap reference = new ConcurrentHashMap();
32+
33+
/** The charcter sequence for output as JSON. */
34+
private final Appendable out;
35+
36+
/** The flag whether the current property is the first item in context or not. */
37+
private boolean first = true;
38+
39+
/**
40+
* JSON serializer.
41+
*
42+
* @param out An output target.
43+
*/
44+
JSON(Appendable out) {
45+
this.out = out;
46+
}
47+
48+
/**
49+
* {@inheritDoc}
50+
*/
51+
@Override
52+
public void walk(Model model, Property property, Object node) {
53+
if (!property.isTransient) {
54+
try {
55+
// ========================================
56+
// Enter Node
57+
// ========================================
58+
// check whether this is first property in current context or not.
59+
if (first) {
60+
// mark as not first
61+
first = false;
62+
} else {
63+
// write property seperator
64+
out.append(',');
65+
}
66+
67+
// write property key (root node and List node doesn't need key)
68+
if (reference.size() != 0 && model.type != List.class) {
69+
write(property.name);
70+
out.append(':');
71+
}
72+
73+
// write property value
74+
if (property.isAttribute()) {
75+
write(I.transform(node, String.class));
76+
} else {
77+
// check cyclic node (non-attribute node only apply this check)
78+
if (reference.putIfAbsent(node, 0) != null) {
79+
throw new ClassCircularityError(reference.toString());
80+
} else {
81+
// write suitable brace
82+
out.append(property.model.type == List.class ? '[' : '{');
83+
}
84+
85+
// ========================================
86+
// Traverse Child Node
87+
// ========================================
88+
boolean store = first; // store the first property state
89+
first = true;
90+
91+
property.model.walk(node, this);
92+
93+
first = store; // restore the first property state
94+
95+
// ========================================
96+
// Leave Node
97+
// ========================================
98+
// unregister non-attribute node
99+
reference.remove(node);
100+
101+
// write suitable brace
102+
out.append(property.model.type == List.class ? ']' : '}');
103+
}
104+
} catch (IOException e) {
105+
throw I.quiet(e);
106+
}
107+
}
108+
}
109+
110+
/**
111+
* <p>
112+
* Write JSON literal with quote.
113+
* </p>
114+
*
115+
* @param value A character sequence.
116+
* @throws IOException
117+
*/
118+
private void write(String value) throws IOException {
119+
out.append('"');
120+
121+
for (int i = 0; i < value.length(); i++) {
122+
char c = value.charAt(i);
123+
124+
switch (c) {
125+
case '"':
126+
out.append("\\\"");
127+
break;
128+
129+
case '\\':
130+
out.append("\\\\");
131+
break;
132+
133+
case '\b':
134+
out.append("\\b");
135+
break;
136+
137+
case '\f':
138+
out.append("\\f");
139+
break;
140+
141+
case '\n':
142+
out.append("\\n");
143+
break;
144+
145+
case '\r':
146+
out.append("\\r");
147+
break;
148+
149+
case '\t':
150+
out.append("\\t");
151+
break;
152+
153+
default:
154+
out.append(c);
155+
}
156+
}
157+
158+
out.append('"');
159+
}
160+
}

src/test/java/kiss/serialization/JSONTest.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import java.io.StringReader;
1414
import java.math.BigInteger;
1515
import java.util.ArrayList;
16+
import java.util.Arrays;
17+
import java.util.Collections;
1618
import java.util.Date;
1719
import java.util.LinkedHashMap;
1820
import java.util.List;
@@ -25,6 +27,7 @@
2527
import kiss.sample.bean.ChainBean;
2628
import kiss.sample.bean.FxPropertyAtField;
2729
import kiss.sample.bean.Group;
30+
import kiss.sample.bean.NestingList;
2831
import kiss.sample.bean.Person;
2932
import kiss.sample.bean.Primitive;
3033
import kiss.sample.bean.School;
@@ -34,7 +37,7 @@
3437
import kiss.sample.bean.TransientBean;
3538

3639
/**
37-
* @version 2016/01/20 10:03:31
40+
* @version 2016/03/15 18:06:36
3841
*/
3942
public class JSONTest {
4043

@@ -161,6 +164,20 @@ public void list() {
161164
assert list.get(2).equals("three");
162165
}
163166

167+
@Test
168+
public void netedList() {
169+
NestingList list = I.make(NestingList.class);
170+
list.setNesting(Arrays.asList(Collections.EMPTY_LIST, Collections.EMPTY_LIST));
171+
172+
// write
173+
String json = json(list);
174+
assert json.equals("{\"nesting\":[[],[]]}");
175+
176+
// read
177+
list = I.read(json, I.make(NestingList.class));
178+
assert list != null;
179+
}
180+
164181
@Test
165182
public void map() {
166183
Map<String, String> map = new LinkedHashMap();

0 commit comments

Comments
 (0)