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:40:32 GMT
Github user jwagenleitner commented on a diff in the pull request:

    https://github.com/apache/groovy/pull/371#discussion_r82402368
  
    --- 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) {
    +            excludeNulls = options.excludeNulls;
    +            disableUnicodeEscaping = options.disableUnicodeEscaping;
    +            nullValue = (excludeNulls) ? JsonOutput.EMPTY_VALUE : JsonOutput.NULL_VALUE;
    +            dateFormat = options.dateFormat;
    +            dateLocale = options.dateLocale;
    +            timezone = options.timezone;
    +            if (!options.converters.isEmpty()) {
    +                converters.addAll(options.converters);
    +                hasConverters = true;
    +            } else {
    +                hasConverters = false;
    +            }
    +            if (!options.excludedFieldNames.isEmpty()) {
    +                excludedFieldNames.addAll(options.excludedFieldNames);
    +                hasExcludedFieldNames = true;
    +            } else {
    +                hasExcludedFieldNames = false;
    +            }
    +            if (!options.excludedFieldTypes.isEmpty()) {
    +                excludedFieldTypes.addAll(options.excludedFieldTypes);
    +                hasExcludedFieldTypes = true;
    +            } else {
    +                hasExcludedFieldTypes = false;
    +            }
    +        }
    +
    +        /**
    +         * @see JsonOutput#toJson(Boolean)
    +         */
    +        public String toJson(Boolean bool) {
    +            CharBuf buffer = CharBuf.create(4);
    +            writeObject(bool, buffer); // checking null inside
    +
    +            return buffer.toString();
    +        }
    +
    +        /**
    +         * @see JsonOutput#toJson(Number)
    +         */
    +        public String toJson(Number n) {
    +            if (n == null) {
    +                return nullValue;
    +            }
    +
    +            CharBuf buffer = CharBuf.create(3);
    +            Class<?> numberClass = n.getClass();
    +
    +            if (shouldExcludeType(numberClass)) {
    +                return EMPTY_VALUE;
    +            }
    +
    +            Converter converter = findConverter(numberClass);
    +            if (converter != null) {
    +                writeRaw(converter.convert(n), buffer);
    +            } else {
    +                writeNumber(numberClass, n, buffer);
    +            }
    +
    +            return buffer.toString();
    +        }
    +
    +        /**
    +         * @see JsonOutput#toJson(Character)
    +         */
    +        public String toJson(Character c) {
    +            CharBuf buffer = CharBuf.create(3);
    +            writeObject(c, buffer); // checking null inside
    +
    +            return buffer.toString();
    +        }
    +
    +        /**
    +         * @see JsonOutput#toJson(String)
    +         */
    +        public String toJson(String s) {
    +            if (s == null) {
    +                return nullValue;
    +            }
    +
    +            CharBuf buffer = CharBuf.create(s.length() + 2);
    +            writeCharSequence(s, buffer);
    +
    +            return buffer.toString();
    +        }
    +
    +        /**
    +         * @see JsonOutput#toJson(Date)
    +         */
    +        public String toJson(Date date) {
    +            if (date == null) {
    +                return nullValue;
    +            }
    +
    +            if (shouldExcludeType(date.getClass())) {
    +                return EMPTY_VALUE;
    +            }
    +
    +            CharBuf buffer = CharBuf.create(26);
    +
    +            Converter converter = findConverter(date.getClass());
    +            if (converter != null) {
    +                writeRaw(converter.convert(date), buffer);
    +            } else {
    +                writeDate(date, buffer);
    +            }
    +
    +            return buffer.toString();
    +        }
    +
    +        /**
    +         * @see JsonOutput#toJson(Calendar)
    +         */
    +        public String toJson(Calendar cal) {
    +            if (cal == null) {
    +                return nullValue;
    +            }
    +
    +            if (shouldExcludeType(cal.getClass())) {
    +                return EMPTY_VALUE;
    +            }
    +
    +            CharBuf buffer = CharBuf.create(26);
    +
    +            Converter converter = findConverter(cal.getClass());
    +            if (converter != null) {
    +                writeRaw(converter.convert(cal), buffer);
    +            } else {
    +                writeDate(cal.getTime(), buffer);
    +            }
    +
    +            return buffer.toString();
    +        }
    +
    +        /**
    +         * @see JsonOutput#toJson(UUID)
    +         */
    +        public String toJson(UUID uuid) {
    +            CharBuf buffer = CharBuf.create(64);
    +            writeObject(uuid, buffer); // checking null inside
    +
    +            return buffer.toString();
    +        }
    +
    +        /**
    +         * @see JsonOutput#toJson(URL)
    +         */
    +        public String toJson(URL url) {
    +            CharBuf buffer = CharBuf.create(64);
    +            writeObject(url, buffer); // checking null inside
    +
    +            return buffer.toString();
    +        }
    +
    +        /**
    +         * @see JsonOutput#toJson(Closure)
    +         */
    +        public String toJson(Closure closure) {
    +            if (closure == null) {
    +                return nullValue;
    +            }
    +
    +            if (shouldExcludeType(closure.getClass())) {
    +                return EMPTY_VALUE;
    +            }
    +
    +            CharBuf buffer = CharBuf.create(255);
    +            writeMap(JsonDelegate.cloneDelegateAndGetContent(closure), buffer);
    +
    +            return buffer.toString();
    +        }
    +
    +        /**
    +         * @see JsonOutput#toJson(Expando)
    +         */
    +        public String toJson(Expando expando) {
    +            if (expando == null) {
    +                return nullValue;
    +            }
    +
    +            if (shouldExcludeType(expando.getClass())) {
    +                return EMPTY_VALUE;
    +            }
    +
    +            CharBuf buffer = CharBuf.create(255);
    +            writeMap(expando.getProperties(), buffer);
    +
    +            return buffer.toString();
    +        }
    +
    +        /**
    +         * @see JsonOutput#toJson(Object)
    +         */
    +        public String toJson(Object object) {
    +            CharBuf buffer = CharBuf.create(255);
    +            writeObject(object, buffer); // checking null inside
    +
    +            return buffer.toString();
    +        }
    +
    +        /**
    +         * @see JsonOutput#toJson(Map)
    +         */
    +        public String toJson(Map m) {
    +            if (m == null) {
    +                return nullValue;
    +            }
    +
    +            if (shouldExcludeType(m.getClass())) {
    +                return EMPTY_VALUE;
    +            }
    +
    +            CharBuf buffer = CharBuf.create(255);
    +            writeMap(m, buffer);
    +
    +            return buffer.toString();
    +        }
    +
    +        /**
    +         * Serializes Number value and writes it into specified buffer.
    +         */
    +        private void writeNumber(Class<?> numberClass, Number value, CharBuf buffer)
{
    +            if (numberClass == Integer.class) {
    +                buffer.addInt((Integer) value);
    +            } else if (numberClass == Long.class) {
    +                buffer.addLong((Long) value);
    +            } else if (numberClass == BigInteger.class) {
    +                buffer.addBigInteger((BigInteger) value);
    +            } else if (numberClass == BigDecimal.class) {
    +                buffer.addBigDecimal((BigDecimal) value);
    +            } else if (numberClass == Double.class) {
    +                Double doubleValue = (Double) value;
    +                if (doubleValue.isInfinite()) {
    +                    throw new JsonException("Number " + value + " can't be serialized
as JSON: infinite are not allowed in JSON.");
    +                }
    +                if (doubleValue.isNaN()) {
    +                    throw new JsonException("Number " + value + " can't be serialized
as JSON: NaN are not allowed in JSON.");
    +                }
    +
    +                buffer.addDouble(doubleValue);
    +            } else if (numberClass == Float.class) {
    +                Float floatValue = (Float) value;
    +                if (floatValue.isInfinite()) {
    +                    throw new JsonException("Number " + value + " can't be serialized
as JSON: infinite are not allowed in JSON.");
    +                }
    +                if (floatValue.isNaN()) {
    +                    throw new JsonException("Number " + value + " can't be serialized
as JSON: NaN are not allowed in JSON.");
    +                }
    +
    +                buffer.addFloat(floatValue);
    +            } else if (numberClass == Byte.class) {
    +                buffer.addByte((Byte) value);
    +            } else if (numberClass == Short.class) {
    +                buffer.addShort((Short) value);
    +            } else { // Handle other Number implementations
    +                buffer.addString(value.toString());
    +            }
    +        }
    +
    +        private void writeObject(Object object, CharBuf buffer) {
    +            writeObject(null, object, buffer);
    +        }
    +
    +        /**
    +         * Serializes object and writes it into specified buffer.
    +         */
    +        private void writeObject(String key, Object object, CharBuf buffer) {
    +            if (object == null) {
    +                if (!excludeNulls) {
    +                    buffer.addNull();
    +                }
    +                return;
    +            }
    +
    +            Class<?> objectClass = object.getClass();
    +
    +            if (shouldExcludeType(objectClass)) {
    +                return;
    +            }
    +
    +            Converter converter = findConverter(objectClass);
    +            if (converter != null) {
    +                writeRaw(converter.convert(object, key), buffer);
    +                return;
    +            }
    +
    +            if (CharSequence.class.isAssignableFrom(objectClass)) { // Handle String,
StringBuilder, GString and other CharSequence implementations
    +                writeCharSequence((CharSequence) object, buffer);
    +            } else if (objectClass == Boolean.class) {
    +                buffer.addBoolean((Boolean) object);
    +            } else if (Number.class.isAssignableFrom(objectClass)) {
    +                writeNumber(objectClass, (Number) object, buffer);
    +            } else if (Date.class.isAssignableFrom(objectClass)) {
    +                writeDate((Date) object, buffer);
    +            } else if (Calendar.class.isAssignableFrom(objectClass)) {
    +                writeDate(((Calendar) object).getTime(), buffer);
    +            } else if (Map.class.isAssignableFrom(objectClass)) {
    +                writeMap((Map) object, buffer);
    +            } else if (Iterable.class.isAssignableFrom(objectClass)) {
    +                writeIterator(((Iterable<?>) object).iterator(), buffer);
    +            } else if (Iterator.class.isAssignableFrom(objectClass)) {
    +                writeIterator((Iterator) object, buffer);
    +            } else if (objectClass == Character.class) {
    +                buffer.addJsonEscapedString(Chr.array((Character) object), disableUnicodeEscaping);
    +            } else if (objectClass == URL.class) {
    +                buffer.addJsonEscapedString(object.toString(), disableUnicodeEscaping);
    +            } else if (objectClass == UUID.class) {
    +                buffer.addQuoted(object.toString());
    +            } else if (objectClass == JsonUnescaped.class) {
    +                buffer.add(object.toString());
    +            } else if (Closure.class.isAssignableFrom(objectClass)) {
    +                writeMap(JsonDelegate.cloneDelegateAndGetContent((Closure<?>) object),
buffer);
    +            } else if (Expando.class.isAssignableFrom(objectClass)) {
    +                writeMap(((Expando) object).getProperties(), buffer);
    +            } else if (Enumeration.class.isAssignableFrom(objectClass)) {
    +                List<?> list = Collections.list((Enumeration<?>) object);
    +                writeIterator(list.iterator(), buffer);
    +            } else if (objectClass.isArray()) {
    +                writeArray(objectClass, object, buffer);
    +            } else if (Enum.class.isAssignableFrom(objectClass)) {
    +                buffer.addQuoted(((Enum<?>) object).name());
    +            }else if (File.class.isAssignableFrom(objectClass)){
    +                Map<?, ?> properties = getObjectProperties(object);
    +                //Clean up all recursive references to File objects
    +                Iterator<? extends Map.Entry<?, ?>> iterator = properties.entrySet().iterator();
    +                while(iterator.hasNext()){
    +                    Map.Entry<?,?> entry = iterator.next();
    +                    if(entry.getValue() instanceof File){
    +                        iterator.remove();
    +                    }
    +                }
    +
    +                writeMap(properties, buffer);
    +            } else {
    +                Map<?, ?> properties = getObjectProperties(object);
    +                writeMap(properties, buffer);
    +            }
    +        }
    +
    +        private static Map<?, ?> getObjectProperties(Object object) {
    +            Map<?, ?> properties = DefaultGroovyMethods.getProperties(object);
    +            properties.remove("class");
    +            properties.remove("declaringClass");
    +            properties.remove("metaClass");
    +            return properties;
    +        }
    +
    +        /**
    +         * Serializes any char sequence and writes it into specified buffer.
    +         */
    +        private void writeCharSequence(CharSequence seq, CharBuf buffer) {
    +            if (seq.length() > 0) {
    +                buffer.addJsonEscapedString(seq.toString(), disableUnicodeEscaping);
    +            } else {
    +                buffer.addChars(EMPTY_STRING_CHARS);
    +            }
    +        }
    +
    +        /**
    +         * Serializes any char sequence and writes it into specified buffer
    +         * without performing any manipulation of the given text.
    +         */
    +        private void writeRaw(CharSequence seq, CharBuf buffer) {
    +            if (seq != null) {
    +                buffer.add(seq.toString());
    +            }
    +        }
    +
    +        /**
    +         * Serializes date and writes it into specified buffer.
    +         */
    +        private void writeDate(Date date, CharBuf buffer) {
    +            SimpleDateFormat formatter = new SimpleDateFormat(dateFormat, dateLocale);
    +            formatter.setTimeZone(timezone);
    +            buffer.addQuoted(formatter.format(date));
    +        }
    +
    +        /**
    +         * Serializes array and writes it into specified buffer.
    +         */
    +        private void writeArray(Class<?> arrayClass, Object array, CharBuf buffer)
{
    +            if (Object[].class.isAssignableFrom(arrayClass)) {
    +                Object[] objArray = (Object[]) array;
    +                writeIterator(Arrays.asList(objArray).iterator(), buffer);
    +                return;
    +            }
    +            buffer.addChar(OPEN_BRACKET);
    +            if (int[].class.isAssignableFrom(arrayClass)) {
    +                int[] intArray = (int[]) array;
    +                if (intArray.length > 0) {
    +                    buffer.addInt(intArray[0]);
    +                    for (int i = 1; i < intArray.length; i++) {
    +                        buffer.addChar(COMMA).addInt(intArray[i]);
    +                    }
    +                }
    +            } else if (long[].class.isAssignableFrom(arrayClass)) {
    +                long[] longArray = (long[]) array;
    +                if (longArray.length > 0) {
    +                    buffer.addLong(longArray[0]);
    +                    for (int i = 1; i < longArray.length; i++) {
    +                        buffer.addChar(COMMA).addLong(longArray[i]);
    +                    }
    +                }
    +            } else if (boolean[].class.isAssignableFrom(arrayClass)) {
    +                boolean[] booleanArray = (boolean[]) array;
    +                if (booleanArray.length > 0) {
    +                    buffer.addBoolean(booleanArray[0]);
    +                    for (int i = 1; i < booleanArray.length; i++) {
    +                        buffer.addChar(COMMA).addBoolean(booleanArray[i]);
    +                    }
    +                }
    +            } else if (char[].class.isAssignableFrom(arrayClass)) {
    +                char[] charArray = (char[]) array;
    +                if (charArray.length > 0) {
    +                    buffer.addJsonEscapedString(Chr.array(charArray[0]), disableUnicodeEscaping);
    +                    for (int i = 1; i < charArray.length; i++) {
    +                        buffer.addChar(COMMA).addJsonEscapedString(Chr.array(charArray[i]),
disableUnicodeEscaping);
    +                    }
    +                }
    +            } else if (double[].class.isAssignableFrom(arrayClass)) {
    +                double[] doubleArray = (double[]) array;
    +                if (doubleArray.length > 0) {
    +                    buffer.addDouble(doubleArray[0]);
    +                    for (int i = 1; i < doubleArray.length; i++) {
    +                        buffer.addChar(COMMA).addDouble(doubleArray[i]);
    +                    }
    +                }
    +            } else if (float[].class.isAssignableFrom(arrayClass)) {
    +                float[] floatArray = (float[]) array;
    +                if (floatArray.length > 0) {
    +                    buffer.addFloat(floatArray[0]);
    +                    for (int i = 1; i < floatArray.length; i++) {
    +                        buffer.addChar(COMMA).addFloat(floatArray[i]);
    +                    }
    +                }
    +            } else if (byte[].class.isAssignableFrom(arrayClass)) {
    +                byte[] byteArray = (byte[]) array;
    +                if (byteArray.length > 0) {
    +                    buffer.addByte(byteArray[0]);
    +                    for (int i = 1; i < byteArray.length; i++) {
    +                        buffer.addChar(COMMA).addByte(byteArray[i]);
    +                    }
    +                }
    +            } else if (short[].class.isAssignableFrom(arrayClass)) {
    +                short[] shortArray = (short[]) array;
    +                if (shortArray.length > 0) {
    +                    buffer.addShort(shortArray[0]);
    +                    for (int i = 1; i < shortArray.length; i++) {
    +                        buffer.addChar(COMMA).addShort(shortArray[i]);
    +                    }
    +                }
    +            }
    +            buffer.addChar(CLOSE_BRACKET);
    +        }
    +
    +        /**
    +         * Serializes map and writes it into specified buffer.
    +         */
    +        private void writeMap(Map<?, ?> map, CharBuf buffer) {
    +            if (!map.isEmpty()) {
    +                buffer.addChar(OPEN_BRACE);
    +                boolean firstItem = true;
    +                for (Map.Entry<?, ?> entry : map.entrySet()) {
    +                    if (entry.getKey() == null) {
    +                        throw new IllegalArgumentException("Maps with null keys can\'t
be converted to JSON");
    +                    }
    +
    +                    String key = entry.getKey().toString();
    +                    Object value = entry.getValue();
    +
    +                    if (excludeNulls && value == null) {
    +                        continue;
    +                    }
    +                    if (hasExcludedFieldNames && excludedFieldNames.contains(key))
{
    +                        continue;
    +                    }
    +                    if (value != null && shouldExcludeType(value.getClass()))
{
    +                        continue;
    +                    }
    +
    +                    if (!firstItem) {
    +                        buffer.addChar(COMMA);
    +                    } else {
    +                        firstItem = false;
    +                    }
    +
    +                    buffer.addJsonFieldName(key, disableUnicodeEscaping);
    +                    writeObject(key, value, buffer);
    +                }
    +                buffer.addChar(CLOSE_BRACE);
    +            } else {
    +                buffer.addChars(EMPTY_MAP_CHARS);
    +            }
    +        }
    +
    +        /**
    +         * Serializes iterator and writes it into specified buffer.
    +         */
    +        private void writeIterator(Iterator<?> iterator, CharBuf buffer) {
    +            if (iterator.hasNext()) {
    +                buffer.addChar(OPEN_BRACKET);
    +                boolean needComma = false;
    +                while (iterator.hasNext()) {
    +                    Object it = iterator.next();
    +                    if (excludeNulls && it == null) {
    +                        continue;
    +                    }
    +                    if (it != null && shouldExcludeType(it.getClass())) {
    +                        continue;
    +                    }
    +                    if (needComma) buffer.addChar(COMMA);
    +                    writeObject(it, buffer);
    +                    needComma = true;
    +                }
    +                buffer.addChar(CLOSE_BRACKET);
    +            } else {
    +                buffer.addChars(EMPTY_LIST_CHARS);
    +            }
    +        }
    +
    +        /**
    +         * Finds a converter that can handle the given type.  The first converter
    +         * that reports it can handle the type is returned, based on the order in
    +         * which the converters were specified.  A {@code null} value will be returned
    +         * if no suitable converter can be found for the given type.
    +         *
    +         * @param type that this converter can handle
    +         * @return first converter that can handle the given type; else {@code null}
    +         *         if no compatible converters are found for the given type.
    +         */
    +        private Converter findConverter(Class<?> type) {
    +            if (!hasConverters) {
    +                return null;
    +            }
    +            for (Converter c : converters) {
    +                if (c.handles(type)) {
    +                    return c;
    +                }
    +            }
    +            return null;
    +        }
    +
    +        /**
    +         * Indicates whether the given type should be excluded from the generated output.
    +         *
    +         * @param type the type to check
    +         * @return {@code true} if the given type should not be output, else {@code false}
    +         */
    +        private boolean shouldExcludeType(Class<?> type) {
    +            if (hasExcludedFieldTypes) {
    +                for (Class<?> t : excludedFieldTypes) {
    +                    if (t.isAssignableFrom(type)) {
    +                        return true;
    +                    }
    +                }
    +            }
    +            return false;
    +        }
    +
    +        /**
    +         * Package-private helper method used by StreamingJsonBuilder.
    +         *
    +         * @param name of the field
    +         * @return true if that field is being excluded, else false
    +         */
    +        boolean isExcludingFieldsNamed(String name) {
    +            return hasExcludedFieldNames && excludedFieldNames.contains(name);
    +        }
    +
    +        /**
    +         * Package-private helper method used by StreamingJsonBuilder.
    +         *
    +         * @param value an instance of an object
    +         * @return true if values like this are being excluded, else false
    +         */
    +        boolean isExcludingValues(Object value) {
    +            if (value == null) {
    +                if (excludeNulls) {
    +                    return true;
    +                }
    +            } else {
    +                if (shouldExcludeType(value.getClass())) {
    +                    return true;
    +                }
    +            }
    +            return false;
    +        }
    +
    +    }
    +
    +    /**
    +     * A converter that handles converting a given type to a JSON value
    +     * using a closure.
    +     */
    +    private static class Converter {
    +
    +        private final Class<?> type;
    +        private final Closure<? extends CharSequence> closure;
    +        private final int paramCount;
    +
    +        static Converter of(Class<?> type, Closure<? extends CharSequence>
closure) {
    +            return new Converter(type, closure);
    +        }
    +
    +        private Converter(Class<?> type, Closure<? extends CharSequence>
closure) {
    +            if (type == null) {
    +                throw new NullPointerException("Type parameter must not be null");
    +            }
    +            if (closure == null) {
    +                throw new NullPointerException("Closure parameter must not be null");
    +            }
    +
    +            int paramCount = closure.getMaximumNumberOfParameters();
    +            if (paramCount < 1) {
    +                throw new IllegalArgumentException("Closure must accept at least one
parameter");
    +            }
    +            Class<?> param1 = closure.getParameterTypes()[0];
    +            if (!param1.isAssignableFrom(type)) {
    +                throw new IllegalArgumentException("Expected first parameter to be of
type: " + type.toString());
    +            }
    +            if (paramCount > 1) {
    +                Class<?> param2 = closure.getParameterTypes()[1];
    +                if (!param2.isAssignableFrom(String.class)) {
    +                    throw new IllegalArgumentException("Expected second parameter to
be of type: " + String.class.toString());
    +                }
    +            }
    +            this.type = type;
    +            this.closure = closure;
    +            this.paramCount = paramCount;
    +        }
    +
    +        /**
    +         * Returns {@code true} if this converter can handle conversions
    +         * of the given type.
    +         *
    +         * @param type the type of the object to convert
    +         * @return true if this converter can successfully convert values of
    +         *      the given type to a JSON value
    +         */
    +        boolean handles(Class<?> type) {
    --- End diff --
    
    I think my rationale here was that since it was a private nested class that is only used
internally as a helper that package-private was the appropriate visibility.  But since the
override methods are public it may make sense to be consistent and make these public too.


---
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