Skip to content
This repository was archived by the owner on Oct 20, 2022. It is now read-only.

Commit b335015

Browse files
Valid Json support
1 parent d06e637 commit b335015

File tree

5 files changed

+298
-159
lines changed

5 files changed

+298
-159
lines changed

src/main/java/com/google/visualization/datasource/DataSourceHelper.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,11 @@ public static String generateResponse(DataTable dataTable, DataSourceRequest dat
286286
break;
287287
case JSONP:
288288
response = JsonRenderer.renderJsonResponse(
289-
dataSourceRequest.getDataSourceParameters(), responseStatus, dataTable, true);
289+
dataSourceRequest.getDataSourceParameters(), responseStatus, dataTable);
290290
break;
291291
case JSON:
292292
response = JsonRenderer.renderJsonResponse(
293-
dataSourceRequest.getDataSourceParameters(), responseStatus, dataTable, false);
293+
dataSourceRequest.getDataSourceParameters(), responseStatus, dataTable);
294294
break;
295295
default:
296296
// This should never happen.
@@ -348,10 +348,10 @@ public static String generateErrorResponse(ResponseStatus responseStatus,
348348
response = HtmlRenderer.renderHtmlError(responseStatus);
349349
break;
350350
case JSONP:
351-
response = JsonRenderer.renderJsonResponse(dsParameters, responseStatus, null, true);
351+
response = JsonRenderer.renderJsonResponse(dsParameters, responseStatus, null);
352352
break;
353353
case JSON:
354-
response = JsonRenderer.renderJsonResponse(dsParameters, responseStatus, null, false);
354+
response = JsonRenderer.renderJsonResponse(dsParameters, responseStatus, null);
355355
break;
356356
default:
357357
// This should never happen.

src/main/java/com/google/visualization/datasource/render/JsonRenderer.java

Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import com.google.common.collect.Lists;
1818
import com.google.visualization.datasource.base.DataSourceParameters;
19+
import com.google.visualization.datasource.base.OutputType;
1920
import com.google.visualization.datasource.base.ReasonType;
2021
import com.google.visualization.datasource.base.ResponseStatus;
2122
import com.google.visualization.datasource.base.StatusType;
@@ -61,7 +62,7 @@ private JsonRenderer() {}
6162
* @return a String-form 64-bit hash of this table.
6263
*/
6364
public static String getSignature(DataTable data) {
64-
String tableAsString = renderDataTable(data, true, false).toString();
65+
String tableAsString = renderDataTable(data, true, false, true).toString();
6566
// Casting to long to avoid bug with abs(Integer.MIN_VALUE) being negative.
6667
long longHashCode = tableAsString.hashCode();
6768
return String.valueOf(Math.abs(longHashCode));
@@ -78,14 +79,14 @@ public static String getSignature(DataTable data) {
7879
private static String getFaultString(ReasonType reasonType, String description) {
7980
List<String> objectParts = Lists.newArrayList();
8081
if (reasonType != null) {
81-
objectParts.add("reason:'" + reasonType.lowerCaseString() + "'");
82-
objectParts.add("message:'" + EscapeUtil.jsonEscape(
83-
reasonType.getMessageForReasonType(null)) + "'");
82+
objectParts.add("\"reason\":\"" + reasonType.lowerCaseString() + "\"");
83+
objectParts.add("\"message\":\"" + EscapeUtil.jsonEscape(
84+
reasonType.getMessageForReasonType(null)) + "\"");
8485
}
8586

8687
if (description != null) {
87-
objectParts.add("detailed_message:'" + EscapeUtil.jsonEscape(description)
88-
+ "'");
88+
objectParts.add("\"detailed_message\":\"" + EscapeUtil.jsonEscape(description)
89+
+ "\"");
8990
}
9091
return new StrBuilder("{").appendWithSeparators(objectParts, ",").append("}").toString();
9192
}
@@ -102,18 +103,18 @@ private static String getFaultString(ReasonType reasonType, String description)
102103
public static CharSequence renderJsonResponse(
103104
DataSourceParameters dsParams,
104105
ResponseStatus responseStatus,
105-
DataTable data,
106-
boolean isJsonp) {
106+
DataTable data) {
107107
StrBuilder sb = new StrBuilder();
108+
boolean isJsonp = dsParams.getOutputType() == OutputType.JSONP;
108109
if (isJsonp) {
109110
sb.append(dsParams.getResponseHandler()).append("(");
110111
}
111-
sb.append("{version:'0.6'");
112+
sb.append("{\"version\":\"0.6\"");
112113

113114
// If no reqId found in the request, do not return reqId in the response.
114115
String requestId = dsParams.getRequestId();
115116
if (requestId != null) {
116-
sb.append(",reqId:'").append(EscapeUtil.jsonEscape(requestId)).append("'");
117+
sb.append(",\"reqId\":\"").append(EscapeUtil.jsonEscape(requestId)).append("\"");
117118
}
118119

119120
// Check signature.
@@ -128,7 +129,7 @@ public static CharSequence renderJsonResponse(
128129
}
129130

130131
StatusType statusType = responseStatus.getStatusType();
131-
sb.append(",status:'").append(statusType.lowerCaseString()).append("'");
132+
sb.append(",\"status\":\"").append(statusType.lowerCaseString()).append("\"");
132133

133134
// There are reason and messages if the status is WARNING/ERROR.
134135
if (statusType != StatusType.OK) {
@@ -141,10 +142,10 @@ public static CharSequence renderJsonResponse(
141142
warningJsonStrings.add(getFaultString(warning.getReasonType(), warning.getMessage()));
142143
}
143144
}
144-
sb.append(",warnings:[").appendWithSeparators(warningJsonStrings, ",").append("]");
145+
sb.append(",\"warnings\":[").appendWithSeparators(warningJsonStrings, ",").append("]");
145146

146147
} else { // Status is error.
147-
sb.append(",errors:[");
148+
sb.append(",\"errors\":[");
148149
sb.append(getFaultString(responseStatus.getReasonType(), responseStatus.getDescription()));
149150
sb.append("]");
150151
}
@@ -153,8 +154,8 @@ public static CharSequence renderJsonResponse(
153154
if ((statusType != StatusType.ERROR) && (data != null)) {
154155
// MessageType OK or WARNING,
155156
// so need to attach a data table (and a signature).
156-
sb.append(",sig:'").append(JsonRenderer.getSignature(data)).append("'");
157-
sb.append(",table:").append(JsonRenderer.renderDataTable(data, true, true));
157+
sb.append(",\"sig\":\"").append(JsonRenderer.getSignature(data)).append("\"");
158+
sb.append(",\"table\":").append(JsonRenderer.renderDataTable(data, true, true, isJsonp));
158159
}
159160

160161
sb.append("}");
@@ -172,11 +173,18 @@ public static CharSequence renderJsonResponse(
172173
* but without the data rows.
173174
* @param includeFormatting False if formatting information should be omitted from the
174175
* generated json.
175-
*
176+
* @param renderDateAsDateConstructor True -> date constructor, False -> date string.
177+
* True if date values should be rendered into the json string as a call to
178+
* Date object constructor (usually used when rendering jsonp string).
179+
* False if it should should be rendered as string.
180+
* For example, when rendering the date 1/1/2011 as Date object constructor its value
181+
* in the json string will be new Date(2011,1,1), and when rendered as string
182+
* will be "Date(2011,1,1)"
183+
*
176184
* @return The char sequence with the Json string.
177185
*/
178186
public static CharSequence renderDataTable(DataTable dataTable, boolean includeValues,
179-
boolean includeFormatting) {
187+
boolean includeFormatting, boolean renderDateAsDateConstructor) {
180188
if (dataTable.getColumnDescriptions().isEmpty()) {
181189
return "";
182190
}
@@ -185,7 +193,7 @@ public static CharSequence renderDataTable(DataTable dataTable, boolean includeV
185193

186194
StringBuilder sb = new StringBuilder();
187195
sb.append("{");
188-
sb.append("cols:["); // column descriptions.
196+
sb.append("\"cols\":["); // column descriptions.
189197

190198
ColumnDescription col;
191199
for (int colId = 0; colId < columnDescriptions.size(); colId++) {
@@ -198,7 +206,7 @@ public static CharSequence renderDataTable(DataTable dataTable, boolean includeV
198206
sb.append("]"); // columns.
199207

200208
if (includeValues) {
201-
sb.append(",rows:[");
209+
sb.append(",\"rows\":[");
202210
List<TableCell> cells;
203211
TableCell cell;
204212
ColumnDescription columnDescription;
@@ -207,23 +215,23 @@ public static CharSequence renderDataTable(DataTable dataTable, boolean includeV
207215
for (int rowId = 0; rowId < rows.size(); rowId++) {
208216
TableRow tableRow = rows.get(rowId);
209217
cells = tableRow.getCells();
210-
sb.append("{c:[");
218+
sb.append("{\"c\":[");
211219
for (int cellId = 0; cellId < cells.size(); cellId++) {
212220
cell = cells.get(cellId);
213221
if (cellId < (cells.size() - 1)) {
214-
appendCellJson(cell, sb, includeFormatting, false);
222+
appendCellJson(cell, sb, includeFormatting, false, renderDateAsDateConstructor);
215223
sb.append(",");
216224
} else {
217225
// Last column in the row.
218-
appendCellJson(cell, sb, includeFormatting, true);
226+
appendCellJson(cell, sb, includeFormatting, true, renderDateAsDateConstructor);
219227
}
220228
}
221229
sb.append("]");
222230

223231
// Row properties.
224232
String customPropertiesString = getPropertiesMapString(tableRow.getCustomProperties());
225233
if (customPropertiesString != null) {
226-
sb.append(",p:").append(customPropertiesString);
234+
sb.append(",\"p\":").append(customPropertiesString);
227235
}
228236

229237
sb.append("}"); // cells.
@@ -238,7 +246,7 @@ public static CharSequence renderDataTable(DataTable dataTable, boolean includeV
238246
// Table properties.
239247
String customPropertiesString = getPropertiesMapString(dataTable.getCustomProperties());
240248
if (customPropertiesString != null) {
241-
sb.append(",p:").append(customPropertiesString);
249+
sb.append(",\"p\":").append(customPropertiesString);
242250
}
243251

244252
sb.append("}"); // table.
@@ -252,11 +260,19 @@ public static CharSequence renderDataTable(DataTable dataTable, boolean includeV
252260
* @param sb The string buffer to append to.
253261
* @param includeFormatting Flase if formatting information should be omitted from the json.
254262
* @param isLastColumn Is this the last column in the row.
263+
* @param renderDateAsDateConstructor True -> date constructor, False -> date string.
264+
* True if date values should be rendered into the json string as a call to
265+
* Date object constructor (usually used when rendering jsonp string).
266+
* False if it should should be rendered as string.
267+
* For example, when rendering the date 1/1/2011 as Date object constructor its value
268+
* in the json string will be new Date(2011,1,1), and when rendered as string
269+
* will be "Date(2011,1,1)"
255270
*
256271
* @return The input string builder.
257272
*/
258273
public static StringBuilder appendCellJson(TableCell cell,
259-
StringBuilder sb, boolean includeFormatting, boolean isLastColumn) {
274+
StringBuilder sb, boolean includeFormatting, boolean isLastColumn,
275+
boolean renderDateAsDateConstructor) {
260276
Value value = cell.getValue();
261277
ValueType type = cell.getType();
262278
StringBuilder valueJson = new StringBuilder();
@@ -276,20 +292,28 @@ public static StringBuilder appendCellJson(TableCell cell,
276292
valueJson.append(((BooleanValue) value).getValue());
277293
break;
278294
case DATE:
279-
valueJson.append("new Date(");
295+
valueJson.append("Date(");
280296
dateValue = (DateValue) value;
281297
valueJson.append(dateValue.getYear()).append(",");
282298
valueJson.append(dateValue.getMonth()).append(",");
283299
valueJson.append(dateValue.getDayOfMonth());
284300
valueJson.append(")");
301+
if (renderDateAsDateConstructor) {
302+
// Rendering date as a call to Date constructor, e.g new Date(2011,1,1)
303+
valueJson.insert(0, "new ");
304+
} else {
305+
// Rendering date in string format, e.g "Date(2011,1,1)"
306+
valueJson.insert(0, "\"");
307+
valueJson.append("\"");
308+
}
285309
break;
286310
case NUMBER:
287311
valueJson.append(((NumberValue) value).getValue());
288312
break;
289313
case TEXT:
290-
valueJson.append("'");
314+
valueJson.append("\"");
291315
valueJson.append(EscapeUtil.jsonEscape(value.toString()));
292-
valueJson.append("'");
316+
valueJson.append("\"");
293317
break;
294318
case TIMEOFDAY:
295319
valueJson.append("[");
@@ -302,7 +326,7 @@ public static StringBuilder appendCellJson(TableCell cell,
302326
break;
303327
case DATETIME:
304328
calendar = ((DateTimeValue) value).getCalendar();
305-
valueJson.append("new Date(");
329+
valueJson.append("Date(");
306330
valueJson.append(calendar.get(GregorianCalendar.YEAR)).append(",");
307331
valueJson.append(calendar.get(GregorianCalendar.MONTH)).append(",");
308332
valueJson.append(calendar.get(GregorianCalendar.DAY_OF_MONTH));
@@ -312,6 +336,14 @@ public static StringBuilder appendCellJson(TableCell cell,
312336
valueJson.append(calendar.get(GregorianCalendar.MINUTE)).append(",");
313337
valueJson.append(calendar.get(GregorianCalendar.SECOND));
314338
valueJson.append(")");
339+
if (renderDateAsDateConstructor) {
340+
// Rendering date as a call to Date constructor, e.g new Date(2011,1,1,0,0,0)
341+
valueJson.insert(0, "new ");
342+
} else {
343+
// Rendering date in string format, e.g "Date(2011,1,1,0,0,0)"
344+
valueJson.insert(0, "\"");
345+
valueJson.append("\"");
346+
}
315347
break;
316348
default:
317349
throw new IllegalArgumentException("Illegal value Type " + type);
@@ -335,14 +367,14 @@ public static StringBuilder appendCellJson(TableCell cell,
335367
if ((isLastColumn) || (!isJsonNull)) {
336368
sb.append("{");
337369
// Value
338-
sb.append("v:").append(valueJson);
370+
sb.append("\"v\":").append(valueJson);
339371
// Formatted value
340372
if ((includeFormatting) && (!escapedFormattedString.equals(""))) {
341-
sb.append(",f:'").append(escapedFormattedString).append("'");
373+
sb.append(",\"f\":\"").append(escapedFormattedString).append("\"");
342374
}
343375
String customPropertiesString = getPropertiesMapString(cell.getCustomProperties());
344376
if (customPropertiesString != null) {
345-
sb.append(",p:").append(customPropertiesString);
377+
sb.append(",\"p\":").append(customPropertiesString);
346378
}
347379
sb.append("}");
348380
}
@@ -360,14 +392,14 @@ public static StringBuilder appendCellJson(TableCell cell,
360392
public static StringBuilder appendColumnDescriptionJson(
361393
ColumnDescription col, StringBuilder sb) {
362394
sb.append("{");
363-
sb.append("id:'").append(EscapeUtil.jsonEscape(col.getId())).append("',");
364-
sb.append("label:'").append(EscapeUtil.jsonEscape(col.getLabel())).append("',");
365-
sb.append("type:'").append(col.getType().getTypeCodeLowerCase()).append("',");
366-
sb.append("pattern:'").append(EscapeUtil.jsonEscape(col.getPattern())).append("'");
395+
sb.append("\"id\":\"").append(EscapeUtil.jsonEscape(col.getId())).append("\",");
396+
sb.append("\"label\":\"").append(EscapeUtil.jsonEscape(col.getLabel())).append("\",");
397+
sb.append("\"type\":\"").append(col.getType().getTypeCodeLowerCase()).append("\",");
398+
sb.append("\"pattern\":\"").append(EscapeUtil.jsonEscape(col.getPattern())).append("\"");
367399

368400
String customPropertiesString = getPropertiesMapString(col.getCustomProperties());
369401
if (customPropertiesString != null) {
370-
sb.append(",p:").append(customPropertiesString);
402+
sb.append(",\"p\":").append(customPropertiesString);
371403
}
372404

373405
sb.append("}");
@@ -386,9 +418,9 @@ private static String getPropertiesMapString(Map<String, String> propertiesMap)
386418
if ((propertiesMap != null) && (!propertiesMap.isEmpty())) {
387419
List<String> customPropertiesStrings = Lists.newArrayList();
388420
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
389-
customPropertiesStrings.add("'"
390-
+ EscapeUtil.jsonEscape(entry.getKey()) + "':'"
391-
+ EscapeUtil.jsonEscape(entry.getValue()) + "'");
421+
customPropertiesStrings.add("\""
422+
+ EscapeUtil.jsonEscape(entry.getKey()) + "\":\""
423+
+ EscapeUtil.jsonEscape(entry.getValue()) + "\"");
392424
}
393425
customPropertiesString = new StrBuilder("{")
394426
.appendWithSeparators(customPropertiesStrings, ",").append("}").toString();

src/test/java/com/google/visualization/datasource/DataSourceHelperTest.java

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,12 @@ public void testGenerateResponse() throws DataSourceException {
184184
new DataSourceParameters(null),
185185
ULocale.UK);
186186
assertEquals(
187-
"{version:'0.6',status:'ok',sig:'730893465',"
188-
+ "table:{cols:[{id:'col1',label:'column1',type:'number',pattern:''},"
189-
+ "{id:'col2',label:'column2',type:'boolean',pattern:''},"
190-
+ "{id:'col3',label:'column3',type:'string',pattern:''}],"
191-
+ "rows:[{c:[{v:7.0},{v:false},{v:'Why?'}]}]}}",
187+
"{\"version\":\"0.6\",\"status\":\"ok\",\"sig\":\"1548939605\","
188+
+ "\"table\":{\"cols\":[{\"id\":\"col1\",\"label\":\"column1\","
189+
+ "\"type\":\"number\",\"pattern\":\"\"},"
190+
+ "{\"id\":\"col2\",\"label\":\"column2\",\"type\":\"boolean\",\"pattern\":\"\"},"
191+
+ "{\"id\":\"col3\",\"label\":\"column3\",\"type\":\"string\",\"pattern\":\"\"}],"
192+
+ "\"rows\":[{\"c\":[{\"v\":7.0},{\"v\":false},{\"v\":\"Why?\"}]}]}}",
192193
DataSourceHelper.generateResponse(dataTable, dataSourceRequest));
193194

194195
// With reqId:666;
@@ -197,11 +198,12 @@ public void testGenerateResponse() throws DataSourceException {
197198
new DataSourceParameters("reqId:666"),
198199
ULocale.UK);
199200
assertEquals(
200-
"{version:'0.6',reqId:'666',status:'ok',sig:'730893465',"
201-
+ "table:{cols:[{id:'col1',label:'column1',type:'number',pattern:''},"
202-
+ "{id:'col2',label:'column2',type:'boolean',pattern:''},"
203-
+ "{id:'col3',label:'column3',type:'string',pattern:''}],"
204-
+ "rows:[{c:[{v:7.0},{v:false},{v:'Why?'}]}]}}",
201+
"{\"version\":\"0.6\",\"reqId\":\"666\",\"status\":\"ok\",\"sig\":\"1548939605\","
202+
+ "\"table\":{\"cols\":[{\"id\":\"col1\",\"label\":\"column1\","
203+
+ "\"type\":\"number\",\"pattern\":\"\"},"
204+
+ "{\"id\":\"col2\",\"label\":\"column2\",\"type\":\"boolean\",\"pattern\":\"\"},"
205+
+ "{\"id\":\"col3\",\"label\":\"column3\",\"type\":\"string\",\"pattern\":\"\"}],"
206+
+ "\"rows\":[{\"c\":[{\"v\":7.0},{\"v\":false},{\"v\":\"Why?\"}]}]}}",
205207
DataSourceHelper.generateResponse(dataTable, dataSourceRequest));
206208

207209
// With out:json;
@@ -210,11 +212,12 @@ public void testGenerateResponse() throws DataSourceException {
210212
new DataSourceParameters("out:json"),
211213
ULocale.UK);
212214
assertEquals(
213-
"{version:'0.6',status:'ok',sig:'730893465',"
214-
+ "table:{cols:[{id:'col1',label:'column1',type:'number',pattern:''},"
215-
+ "{id:'col2',label:'column2',type:'boolean',pattern:''},"
216-
+ "{id:'col3',label:'column3',type:'string',pattern:''}],"
217-
+ "rows:[{c:[{v:7.0},{v:false},{v:'Why?'}]}]}}",
215+
"{\"version\":\"0.6\",\"status\":\"ok\",\"sig\":\"1548939605\","
216+
+ "\"table\":{\"cols\":[{\"id\":\"col1\",\"label\":\"column1\","
217+
+ "\"type\":\"number\",\"pattern\":\"\"},"
218+
+ "{\"id\":\"col2\",\"label\":\"column2\",\"type\":\"boolean\",\"pattern\":\"\"},"
219+
+ "{\"id\":\"col3\",\"label\":\"column3\",\"type\":\"string\",\"pattern\":\"\"}],"
220+
+ "\"rows\":[{\"c\":[{\"v\":7.0},{\"v\":false},{\"v\":\"Why?\"}]}]}}",
218221
DataSourceHelper.generateResponse(dataTable, dataSourceRequest));
219222

220223
// With out:jsonp;
@@ -223,11 +226,13 @@ public void testGenerateResponse() throws DataSourceException {
223226
new DataSourceParameters("out:jsonp"),
224227
ULocale.UK);
225228
assertEquals(
226-
"google.visualization.Query.setResponse({version:'0.6',status:'ok',"
227-
+ "sig:'730893465',table:{cols:[{id:'col1',label:'column1',type:'number',pattern:''},"
228-
+ "{id:'col2',label:'column2',type:'boolean',pattern:''},"
229-
+ "{id:'col3',label:'column3',type:'string',pattern:''}],"
230-
+ "rows:[{c:[{v:7.0},{v:false},{v:'Why?'}]}]}});",
229+
"google.visualization.Query.setResponse("
230+
+ "{\"version\":\"0.6\",\"status\":\"ok\",\"sig\":\"1548939605\","
231+
+ "\"table\":{\"cols\":[{\"id\":\"col1\",\"label\":\"column1\","
232+
+ "\"type\":\"number\",\"pattern\":\"\"},"
233+
+ "{\"id\":\"col2\",\"label\":\"column2\",\"type\":\"boolean\",\"pattern\":\"\"},"
234+
+ "{\"id\":\"col3\",\"label\":\"column3\",\"type\":\"string\",\"pattern\":\"\"}],"
235+
+ "\"rows\":[{\"c\":[{\"v\":7.0},{\"v\":false},{\"v\":\"Why?\"}]}]}});",
231236
DataSourceHelper.generateResponse(dataTable, dataSourceRequest));
232237

233238
// Now with out:csv;

0 commit comments

Comments
 (0)