groovy-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jwagenleitner <...@git.apache.org>
Subject [GitHub] groovy pull request #371: Serialization options for JsonOutput
Date Fri, 07 Oct 2016 14:26:04 GMT
Github user jwagenleitner commented on a diff in the pull request:

    https://github.com/apache/groovy/pull/371#discussion_r82399398
  
    --- 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 --
    
    Yes, that's a good idea, I'll make the constructor and `write*` methods protected.
    
    To me it's starting to feel like the generator should be an interface (e.g., `JsonGenerator`)
and having an abstract and default implementation that can be extended if desired.  But I
don't want to go overboard on designing this so would be interested in your opinion and @paulk-asert
.


---
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 infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

Mime
View raw message