From 48ea22d718610a31b65ce59c4c8b47cfa3cbb7a0 Mon Sep 17 00:00:00 2001 From: lukaseder Date: Mon, 4 Jul 2016 14:35:35 +0200 Subject: [PATCH] [#5372] Add Result.formatJSON(JSONFormat) to allow for different JSON formats --- jOOQ/src/main/java/org/jooq/JSONFormat.java | 145 ++++++++++++++++++ jOOQ/src/main/java/org/jooq/Result.java | 23 +++ .../main/java/org/jooq/impl/ResultImpl.java | 99 ++++++++---- 3 files changed, 239 insertions(+), 28 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/JSONFormat.java diff --git a/jOOQ/src/main/java/org/jooq/JSONFormat.java b/jOOQ/src/main/java/org/jooq/JSONFormat.java new file mode 100644 index 0000000000..38c9cba06e --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/JSONFormat.java @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2009-2016, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq; + +/** + * A JSON formatting type, which can be used to configure JSON imports / + * exports. + *

+ * The default format is the following, using {@link #header()} equal to + * true and applying {@link RecordFormat#ARRAY}:

+ * {"fields":[{"name":"field-1","type":"type-1"},
+ *            {"name":"field-2","type":"type-2"},
+ *             ...,
+ *            {"name":"field-n","type":"type-n"}],
+ * "records":[[value-1-1,value-1-2,...,value-1-n],
+ *            [value-2-1,value-2-2,...,value-2-n]]}
+ *

+ * If {@link #header()} is set to false, then the result is simply + * the records array, either using {@link RecordFormat#ARRAY}:

+ * [[value-1-1,value-1-2,...,value-1-n],
+ *  [value-2-1,value-2-2,...,value-2-n]]
+ *

+ * or, using {@link RecordFormat#OBJECT}:

+ * [{"field-1": value-1-1, "field-2": value-1-2,..., "field-n": value-1-n},
+ *  {"field-1": value-2-1, "field-2": value-2-2,..., "field-n": value-2-n}]
+ * + * @author Lukas Eder + */ +public final class JSONFormat { + + final boolean header; + final RecordFormat recordFormat; + + public JSONFormat() { + this( + true, + RecordFormat.ARRAY + ); + } + + private JSONFormat( + boolean header, + RecordFormat recordFormat) { + this.header = header; + this.recordFormat = recordFormat; + } + + /** + * Whether to emit a header row with column names, defaulting to + * true. + */ + public JSONFormat header(boolean newHeader) { + return new JSONFormat( + newHeader, + recordFormat); + } + + /** + * Whether to emit a header row with column names, defaulting to + * true. + */ + public boolean header() { + return header; + } + + /** + * The record format to be applied, defaulting to + * {@link RecordFormat#ARRAY}. + */ + public JSONFormat recordFormat(RecordFormat newRecordFormat) { + return new JSONFormat( + header, + newRecordFormat); + } + + /** + * The record format to be applied, defaulting to + * {@link RecordFormat#ARRAY}. + */ + public RecordFormat recordFormat() { + return recordFormat; + } + + /** + * The format of individual JSON records. + */ + public enum RecordFormat { + + /** + * A record is a JSON array. + *

+ * This format allows for accessing columns by index, saving space by + * avoiding repetitive column names in large result sets. Use this + * preferrably with {@link JSONFormat#header()} equal to + * true. + */ + ARRAY, + + /** + * A record is a JSON object. + *

+ * This format allows for accessing columns by name, repeating column + * names in each record. + */ + OBJECT, + } +} diff --git a/jOOQ/src/main/java/org/jooq/Result.java b/jOOQ/src/main/java/org/jooq/Result.java index e57d040a18..35f8e1ee0d 100644 --- a/jOOQ/src/main/java/org/jooq/Result.java +++ b/jOOQ/src/main/java/org/jooq/Result.java @@ -607,6 +607,15 @@ public interface Result extends List, Attachable { */ String formatJSON(); + /** + * Get a simple formatted representation of this result as a JSON data + * structure, according to the format. + * + * @return The formatted result + * @see JSONFormat + */ + String formatJSON(JSONFormat format); + /** * Get this result formatted as XML. * @@ -711,6 +720,13 @@ public interface Result extends List, Attachable { */ void formatJSON(OutputStream stream) throws IOException; + /** + * Like {@link #formatJSON(JSONFormat)}, but the data is output onto an {@link OutputStream}. + * + * @throws IOException - an unchecked wrapper for {@link java.io.IOException}, if anything goes wrong. + */ + void formatJSON(OutputStream stream, JSONFormat format) throws IOException; + /** * Like {@link #formatXML()}, but the data is output onto an {@link OutputStream}. * @@ -809,6 +825,13 @@ public interface Result extends List, Attachable { */ void formatJSON(Writer writer) throws IOException; + /** + * Like {@link #formatJSON(JSONFormat)}, but the data is output onto a {@link Writer}. + * + * @throws IOException - an unchecked wrapper for {@link java.io.IOException}, if anything goes wrong. + */ + void formatJSON(Writer writer, JSONFormat format) throws IOException; + /** * Like {@link #formatXML()}, but the data is output onto a {@link Writer}. * diff --git a/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java b/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java index d2a6fea64b..13b1bfeac9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java @@ -86,6 +86,7 @@ import org.jooq.DataType; import org.jooq.EnumType; import org.jooq.Field; import org.jooq.ForeignKey; +import org.jooq.JSONFormat; import org.jooq.Name; import org.jooq.Record; import org.jooq.Record1; @@ -125,6 +126,7 @@ import org.jooq.exception.InvalidResultException; import org.jooq.tools.Convert; import org.jooq.tools.StringUtils; import org.jooq.tools.jdbc.MockResultSet; +import org.jooq.tools.json.JSONArray; import org.jooq.tools.json.JSONObject; import org.w3c.dom.Document; @@ -870,57 +872,98 @@ final class ResultImpl implements Result, AttachableInterna return writer.toString(); } + @Override + public final String formatJSON(JSONFormat format) { + StringWriter writer = new StringWriter(); + formatJSON(writer, format); + return writer.toString(); + } + @Override public final void formatJSON(OutputStream stream) { formatJSON(new OutputStreamWriter(stream)); } + @Override + public final void formatJSON(OutputStream stream, JSONFormat format) { + formatJSON(new OutputStreamWriter(stream), format); + } + @Override public final void formatJSON(Writer writer) { + formatJSON(writer, new JSONFormat()); + } + + @Override + public final void formatJSON(Writer writer, JSONFormat format) { try { - List> f = new ArrayList>(); - List> r = new ArrayList>(); + List> f = null; + List r = new ArrayList(); - Map fieldMap; - for (Field field : fields.fields) { - fieldMap = new LinkedHashMap(); + if (format.header()) { + f = new ArrayList>(); - if (field instanceof TableField) { - Table table = ((TableField) field).getTable(); + for (Field field : fields.fields) { + Map fieldMap = new LinkedHashMap(); - if (table != null) { - Schema schema = table.getSchema(); + if (field instanceof TableField) { + Table table = ((TableField) field).getTable(); - if (schema != null) { - fieldMap.put("schema", schema.getName()); + if (table != null) { + Schema schema = table.getSchema(); + + if (schema != null) + fieldMap.put("schema", schema.getName()); + + fieldMap.put("table", table.getName()); } - - fieldMap.put("table", table.getName()); } + + fieldMap.put("name", field.getName()); + fieldMap.put("type", field.getDataType().getTypeName().toUpperCase()); + + f.add(fieldMap); } - - fieldMap.put("name", field.getName()); - fieldMap.put("type", field.getDataType().getTypeName().toUpperCase()); - - f.add(fieldMap); } - for (Record record : this) { - List list = new ArrayList(); + switch (format.recordFormat()) { + case ARRAY: + for (Record record : this) { + List list = new ArrayList(); - for (int index = 0; index < fields.fields.length; index++) { - list.add(formatJSON0(record.get(index))); - } + for (int index = 0; index < fields.fields.length; index++) + list.add(formatJSON0(record.get(index))); - r.add(list); + r.add(list); + } + + break; + case OBJECT: + for (Record record : this) { + Map map = new LinkedHashMap(); + + for (int index = 0; index < fields.fields.length; index++) + map.put(record.field(index).getName(), formatJSON0(record.get(index))); + + r.add(map); + } + + break; + default: + throw new IllegalArgumentException("Format not supported: " + format); } - Map> map = new LinkedHashMap>(); + if (f == null) { + writer.append(JSONArray.toJSONString(r)); + } + else { + Map> map = new LinkedHashMap>(); - map.put("fields", f); - map.put("records", r); + map.put("fields", f); + map.put("records", r); - writer.append(JSONObject.toJSONString(map)); + writer.append(JSONObject.toJSONString(map)); + } writer.flush(); }