Github user graemerocher commented on a diff in the pull request:
https://github.com/apache/groovy/pull/371#discussion_r81288503
--- Diff: subprojects/groovy-json/src/main/java/groovy/json/JsonOutput.java
---
@@ -601,4 +280,966 @@ public String toString() {
}
}
+ /**
+ * Creates a builder for various options that can be set to alter the
+ * generated JSON. After setting the options a call to
+ * {@link Options#createGenerator()} will return a fully configured
+ * {@link JsonOutput.Generator} object and the {@code toJson} methods
+ * can be used.
+ *
+ * @return a builder for building a JsonOutput.Generator
+ * with the specified options set.
+ * @since 2.5
+ */
+ public static Options options() {
+ return new Options();
+ }
+
+ /**
+ * A builder used to construct a {@link JsonOutput.Generator} instance
that allows
+ * control over the serialized JSON output. If you do not need to
customize the
+ * output it is recommended to use the static {@code
JsonOutput.toJson} methods.
+ *
+ * <p>
+ * Example:
+ * <pre><code class="groovyTestCase">
+ * def generator = groovy.json.JsonOutput.options()
+ * .excludeNulls()
+ * .dateFormat('yyyy')
+ * .excludeFieldsByName('bar', 'baz')
+ * .excludeFieldsByType(java.sql.Date)
+ * .createGenerator()
+ *
+ * def input = [foo: null, lastUpdated: Date.parse('yyyy-MM-dd',
'2014-10-24'),
+ * bar: 'foo', baz: 'foo', systemDate: new
java.sql.Date(new Date().getTime())]
+ *
+ * assert generator.toJson(input) == '{"lastUpdated":"2014"}'
+ * </code></pre>
+ *
+ * @since 2.5
+ */
+ public static class Options {
+
+ private boolean excludeNulls;
+
+ private boolean disableUnicodeEscaping;
+
+ private String dateFormat = JsonOutput.JSON_DATE_FORMAT;
+
+ private Locale dateLocale = JsonOutput.JSON_DATE_FORMAT_LOCALE;
+
+ private TimeZone timezone =
TimeZone.getTimeZone(JsonOutput.DEFAULT_TIMEZONE);
+
+ private final Set<Converter> converters = new
LinkedHashSet<Converter>();
+
+ private final Set<String> excludedFieldNames = new
HashSet<String>();
+
+ private final Set<Class<?>> excludedFieldTypes = new
HashSet<Class<?>>();
+
+ private Options() {}
+
+ /**
+ * Do not serialize {@code null} values.
+ *
+ * @return a reference to this {@code Options} instance
+ */
+ public Options excludeNulls() {
+ excludeNulls = true;
+ return this;
+ }
+
+ /**
+ * Disables the escaping of Unicode characters in JSON String
values.
+ *
+ * @return a reference to this {@code Options} instance
+ */
+ public Options disableUnicodeEscaping() {
+ disableUnicodeEscaping = true;
+ return this;
+ }
+
+ /**
+ * Sets the date format that will be used to serialize {@code
Date} objects.
+ * This must be a valid pattern for {@link
java.text.SimpleDateFormat} and the
+ * date formatter will be constructed with the default locale of
{@link Locale#US}.
+ *
+ * @param format date format pattern used to serialize dates
+ * @return a reference to this {@code Options} instance
+ * @exception NullPointerException if the given pattern is null
+ * @exception IllegalArgumentException if the given pattern is
invalid
+ */
+ public Options dateFormat(String format) {
+ return dateFormat(format, JsonOutput.JSON_DATE_FORMAT_LOCALE);
+ }
+
+ /**
+ * Sets the date format that will be used to serialize {@code
Date} objects.
+ * This must be a valid pattern for {@link
java.text.SimpleDateFormat}.
+ *
+ * @param format date format pattern used to serialize dates
+ * @param locale the locale whose date format symbols will be used
+ * @return a reference to this {@code Options} instance
+ * @exception IllegalArgumentException if the given pattern is
invalid
+ */
+ public Options dateFormat(String format, Locale locale) {
+ // validate date format pattern
+ new SimpleDateFormat(format, locale);
+ dateFormat = format;
+ dateLocale = locale;
+ return this;
+ }
+
+ /**
+ * Sets the time zone that will be used to serialize dates.
+ *
+ * @param timezone used to serialize dates
+ * @return a reference to this {@code Options} instance
+ * @exception NullPointerException if the given timezone is null
+ */
+ public Options timezone(String timezone) {
+ this.timezone = TimeZone.getTimeZone(timezone);
+ return this;
+ }
+
+ /**
+ * Registers a closure that will be called when the specified type
or subtype
+ * is serialized.
+ *
+ * <p>The closure must accept either 1 or 2 parameters. The first
parameter
+ * is required and will be instance of the {@code type} for which
the closure
+ * is registered. The second optional parameter should be of type
{@code String}
+ * and, if available, will be passed the name of the key
associated with this
+ * value if serializing a JSON Object. This parameter will be
{@code null} when
+ * serializing a JSON Array or when there is no way to determine
the name of the key.
+ *
+ * <p>The return value from the closure must be a valid JSON
value. The result
+ * of the closure will be written to the internal buffer directly
and no quoting,
+ * escaping or other manipulation will be done to the resulting
output.
+ *
+ * <p>
+ * Example:
+ * <pre><code class="groovyTestCase">
+ * def generator = groovy.json.JsonOutput.options()
+ * .addConverter(URL) { URL u ->
+ * "\"${u.getHost()}\""
+ * }
+ * .createGenerator()
+ *
+ * def input = [domain: new
URL('http://groovy-lang.org/json.html#_parser_variants')]
+ *
+ * assert generator.toJson(input) ==
'{"domain":"groovy-lang.org"}'
+ * </code></pre>
+ *
+ * <p>If two or more closures are registered for the exact same
type the last
+ * closure based on the order they were specified will be used.
When serializing an
+ * object its type is compared to the list of registered types in
the order the were
+ * given and the closure for the first suitable type will be
called. Therefore, it is
+ * important to register more specific types first.
+ *
+ * @param type the type to convert
+ * @param closure called when the registered type or any type
assignable to the given
+ * type is encountered
+ * @param <T> the type this converter is registered to handle
+ * @return a reference to this {@code Options} instance
+ * @exception NullPointerException if the given type or closure is
null
+ * @exception IllegalArgumentException if the given closure does
not accept
+ * a parameter of the given type
+ */
+ public <T> Options addConverter(Class<T> type,
@ClosureParams(value=FromString.class, options={"T","T,String"}) Closure<?
extends CharSequence> closure) {
+ Converter converter = Converter.of(type, closure);
+ if (converters.contains(converter)) {
+ converters.remove(converter);
+ }
+ converters.add(converter);
+ return this;
+ }
+
+ /**
+ * Excludes from the output any fields that match the specified
names.
+ *
+ * @param fieldNames name of the field to exclude from the output
+ * @return a reference to this {@code Options} instance
+ */
+ public Options excludeFieldsByName(CharSequence... fieldNames) {
+ return excludeFieldsByName(Arrays.asList(fieldNames));
+ }
+
+ /**
+ * Excludes from the output any fields that match the specified
names.
+ *
+ * @param fieldNames collection of names to exclude from the output
+ * @return a reference to this {@code Options} instance
+ */
+ public Options excludeFieldsByName(Iterable<? extends
CharSequence> fieldNames) {
+ for (CharSequence cs : fieldNames) {
+ if (cs != null) {
+ excludedFieldNames.add(cs.toString());
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Excludes from the output any fields whose type is the same or is
+ * assignable to any of the given types.
+ *
+ * @param types excluded from the output
+ * @return a reference to this {@code Options} instance
+ */
+ public Options excludeFieldsByType(Class<?>... types) {
+ return excludeFieldsByType(Arrays.asList(types));
+ }
+
+ /**
+ * Excludes from the output any fields whose type is the same or is
+ * assignable to any of the given types.
+ *
+ * @param types collection of types to exclude from the output
+ * @return a reference to this {@code Options} instance
+ */
+ public Options excludeFieldsByType(Iterable<Class<?>> types) {
+ for (Class<?> c : types) {
+ if (c != null) {
+ excludedFieldTypes.add(c);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Creates a {@link JsonOutput.Generator} that is based on the
current options.
+ *
+ * @return a fully configured {@link JsonOutput.Generator}
+ */
+ public Generator createGenerator() {
+ return new Generator(this);
+ }
+ }
+
+ /**
+ * A JsonOutput Generator that can be configured with various {@link
JsonOutput.Options}.
+ * If the default options are sufficient consider using the static
{@code JsonOutput.toJson}
+ * methods.
+ *
+ * @see JsonOutput#options()
+ * @see Options#createGenerator()
+ * @since 2.5
+ */
+ public static class Generator {
+
+ private final boolean excludeNulls;
+ private final boolean disableUnicodeEscaping;
+ private final String dateFormat;
+ private final Locale dateLocale;
+ private final TimeZone timezone;
+
+ private final Set<Converter> converters = new
LinkedHashSet<Converter>();
+
+ private final Set<String> excludedFieldNames = new
HashSet<String>();
+
+ private final Set<Class<?>> excludedFieldTypes = new
HashSet<Class<?>>();
+
+ private final String nullValue;
+
+ private final boolean hasConverters;
+ private final boolean hasExcludedFieldNames;
+ private final boolean hasExcludedFieldTypes;
+
+ private Generator(Options options) {
--- End diff --
Could we make this `protected` so subclasses could further customize as
needed?
---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at [email protected] or file a JIRA ticket
with INFRA.
---