org.jasonjson.core.JasonBuilder Maven / Gradle / Ivy
Show all versions of jason Show documentation
/*
* Copyright (C) 2008 Google Inc.
*
* 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.
*/
package org.jasonjson.core;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jasonjson.core.internal.$Gson$Preconditions;
import org.jasonjson.core.internal.Excluder;
import org.jasonjson.core.internal.bind.TypeAdapters;
import org.jasonjson.core.reflect.TypeToken;
import org.jasonjson.core.AccessStrategy;
import org.jasonjson.core.AnnotationDefinedAccessStrategy;
/**
* Use this builder to construct a {@link Jason} instance when you need to set configuration
* options other than the default. For {@link Jason} with default configuration, it is simpler to
* use {@literal new Jason()}. {@literal JasonBuilder} is best used by creating it, and then invoking its
* various configuration methods, and finally calling create.
*
* The following is an example shows how to use the {@literal JasonBuilder} to construct a Jason
instance:
Jason gson = new JasonBuilder()
.registerTypeAdapter(Id.class, new IdTypeAdapter())
.enableComplexMapKeySerialization()
.serializeNulls()
.setDateFormat(DateFormat.LONG)
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.setPrettyPrinting()
.setVersion(1.0)
.create();
*
* NOTES:
*
* - the order of invocation of configuration methods does not matter.
* - The default serialization of {@link Date} and its subclasses in Jason does
not contain time-zone information. So, if you are using date/time instances,
use {@literal JasonBuilder} and its {@literal setDateFormat} methods.
*
*
*
* @author Inderjeet Singh
* @author Joel Leitch
* @author Jesse Wilson
*/
public final class JasonBuilder {
private Excluder excluder = Excluder.DEFAULT;
private LongSerializationPolicy longSerializationPolicy = LongSerializationPolicy.DEFAULT;
private FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
private AccessStrategy accessStrategy = new AnnotationDefinedAccessStrategy();
private final Map> instanceCreators
= new HashMap>();
private final List factories = new ArrayList();
/** tree-style hierarchy factories. These come after factories for backwards compatibility. */
private final List hierarchyFactories = new ArrayList();
private boolean serializeNulls;
private String datePattern;
private int dateStyle = DateFormat.DEFAULT;
private int timeStyle = DateFormat.DEFAULT;
private boolean complexMapKeySerialization;
private boolean serializeSpecialFloatingPointValues;
private boolean escapeHtmlChars = true;
private boolean prettyPrinting;
private boolean generateNonExecutableJson;
/**
* Creates a JasonBuilder instance that can be used to build Jason with various configuration
settings. JasonBuilder follows the builder pattern, and it is typically used by first
invoking various configuration methods to set desired options, and finally calling
{@link #create()}.
*/
public JasonBuilder() {
}
/**
* Configures Jason to enable versioning support.
*
* @param ignoreVersionsAfter any field or type marked with a version higher than this value
* are ignored during serialization or deserialization.
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
*/
public JasonBuilder setVersion(double ignoreVersionsAfter) {
excluder = excluder.withVersion(ignoreVersionsAfter);
return this;
}
/**
* Configures Jason to excludes all class fields that have the specified modifiers. By default,
Jason will exclude all fields marked transient or static. This method will override that
behavior.
*
* @param modifiers the field modifiers. You must use the modifiers specified in the
* {@link java.lang.reflect.Modifier} class. For example,
* {@link java.lang.reflect.Modifier#TRANSIENT},
* {@link java.lang.reflect.Modifier#STATIC}.
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
*/
public JasonBuilder excludeFieldsWithModifiers(int... modifiers) {
excluder = excluder.withModifiers(modifiers);
return this;
}
/**
* Makes the output JSON non-executable in Javascript by prefixing the generated JSON with some
* special text. This prevents attacks from third-party sites through script sourcing. See
* Jason Issue 42
* for details.
*
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
*/
public JasonBuilder generateNonExecutableJson() {
this.generateNonExecutableJson = true;
return this;
}
/**
* Configures Jason to exclude all fields from consideration for serialization or deserialization
that do not have the {@link com.google.gson.annotations.Expose} annotation.
*
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
*/
public JasonBuilder excludeFieldsWithoutExposeAnnotation() {
excluder = excluder.excludeFieldsWithoutExposeAnnotation();
return this;
}
/**
* Configure Jason to serialize null fields. By default, Jason omits all fields that are null
during serialization.
*
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.2
*/
public JasonBuilder serializeNulls() {
this.serializeNulls = true;
return this;
}
/**
* Enabling this feature will only change the serialized form if the map key is
* a complex type (i.e. non-primitive) in its serialized JSON
* form. The default implementation of map serialization uses {@literal toString()}
* on the key; however, when this is called then one of the following cases
* apply:
*
* Maps as JSON objects
* For this case, assume that a type adapter is registered to serialize and
* deserialize some {@literal Point} class, which contains an x and y coordinate,
* to/from the JSON Primitive string value {@literal "(x,y)"}. The Java map would
* then be serialized as a {@link JsonObject}.
*
* Below is an example:
*
{@literal Jason gson = new JasonBuilder()
.register(Point.class, new MyPointTypeAdapter())
.enableComplexMapKeySerialization()
.create();
Map original = new LinkedHashMap();
original.put(new Point(5, 6), "a");
original.put(new Point(8, 8), "b");
System.out.println(gson.toJson(original, type));
}
* The above code prints this JSON object: {@literal {
"(5,6)": "a",
"(8,8)": "b"
}
}
*
* Maps as JSON arrays
* For this case, assume that a type adapter was NOT registered for some
* {@literal Point} class, but rather the default Jason serialization is applied.
In this case, some {@literal new Point(2,3)} would serialize as {@literal {"x":2,"y":5}}.
*
* Given the assumption above, a {@literal Map} will be
* serialize as an array of arrays (can be viewed as an entry set of pairs).
*
* Below is an example of serializing complex types as JSON arrays:
*
{@literal Jason gson = new JasonBuilder()
.enableComplexMapKeySerialization()
.create();
Map original = new LinkedHashMap();
original.put(new Point(5, 6), "a");
original.put(new Point(8, 8), "b");
System.out.println(gson.toJson(original, type));
}
*
* The JSON output would look as follows:
* {@literal [
[
{
"x": 5,
"y": 6
},
"a"
],
[
{
"x": 8,
"y": 8
},
"b"
]
]
}
*
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.7
*/
public JasonBuilder enableComplexMapKeySerialization() {
complexMapKeySerialization = true;
return this;
}
/**
* Configures Jason to exclude inner classes during serialization.
*
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
*/
public JasonBuilder disableInnerClassSerialization() {
excluder = excluder.disableInnerClassSerialization();
return this;
}
/**
* Configures Jason to apply a specific serialization policy for {@literal Long} and {@literal long}
* objects.
*
* @param serializationPolicy the particular policy to use for serializing longs.
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
*/
public JasonBuilder setLongSerializationPolicy(LongSerializationPolicy serializationPolicy) {
this.longSerializationPolicy = serializationPolicy;
return this;
}
/**
* Configures Jason to apply a specific naming policy to an object's field during serialization
and deserialization.
*
* @param namingConvention the JSON field naming convention to use for serialization and
* deserialization.
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
*/
public JasonBuilder setFieldNamingPolicy(FieldNamingPolicy namingConvention) {
this.fieldNamingPolicy = namingConvention;
return this;
}
/**
* Configures Jason to apply a specific naming policy strategy to an object's field during
serialization and deserialization.
*
* @param fieldNamingStrategy the actual naming strategy to apply to the fields
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
*/
public JasonBuilder setFieldNamingStrategy(FieldNamingStrategy fieldNamingStrategy) {
this.fieldNamingPolicy = fieldNamingStrategy;
return this;
}
public JasonBuilder setAccessStrategy(AccessStrategy accessStrategy) {
this.accessStrategy = accessStrategy;
return this;
}
/**
* Configures Jason to apply a set of exclusion strategies during both serialization and
deserialization. Each of the {@literal strategies} will be applied as a disjunction rule.
* This means that if one of the {@literal strategies} suggests that a field (or class) should be
* skipped then that field (or object) is skipped during serializaiton/deserialization.
*
* @param strategies the set of strategy object to apply during object (de)serialization.
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.4
*/
public JasonBuilder setExclusionStrategies(ExclusionStrategy... strategies) {
for (ExclusionStrategy strategy : strategies) {
excluder = excluder.withExclusionStrategy(strategy, true, true);
}
return this;
}
/**
* Configures Jason to apply the passed in exclusion strategy during serialization.
* If this method is invoked numerous times with different exclusion strategy objects
* then the exclusion strategies that were added will be applied as a disjunction rule.
* This means that if one of the added exclusion strategies suggests that a field (or
* class) should be skipped then that field (or object) is skipped during its
* serialization.
*
* @param strategy an exclusion strategy to apply during serialization.
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.7
*/
public JasonBuilder addSerializationExclusionStrategy(ExclusionStrategy strategy) {
excluder = excluder.withExclusionStrategy(strategy, true, false);
return this;
}
/**
* Configures Jason to apply the passed in exclusion strategy during deserialization.
* If this method is invoked numerous times with different exclusion strategy objects
* then the exclusion strategies that were added will be applied as a disjunction rule.
* This means that if one of the added exclusion strategies suggests that a field (or
* class) should be skipped then that field (or object) is skipped during its
* deserialization.
*
* @param strategy an exclusion strategy to apply during deserialization.
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.7
*/
public JasonBuilder addDeserializationExclusionStrategy(ExclusionStrategy strategy) {
excluder = excluder.withExclusionStrategy(strategy, false, true);
return this;
}
/**
* Configures Jason to output Json that fits in a page for pretty printing. This option only
* affects Json serialization.
*
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
*/
public JasonBuilder setPrettyPrinting() {
prettyPrinting = true;
return this;
}
/**
* By default, Jason escapes HTML characters such as < > etc. Use this option to configure
Jason to pass-through HTML characters as is.
*
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
*/
public JasonBuilder disableHtmlEscaping() {
this.escapeHtmlChars = false;
return this;
}
/**
* Configures Jason to serialize {@literal Date} objects according to the pattern provided. You can
* call this method or {@link #setDateFormat(int)} multiple times, but only the last invocation
* will be used to decide the serialization format.
*
* The date format will be used to serialize and deserialize {@link java.util.Date}, {@link
* java.sql.Timestamp} and {@link java.sql.Date}.
*
*
Note that this pattern must abide by the convention provided by {@literal SimpleDateFormat}
* class. See the documentation in {@link java.text.SimpleDateFormat} for more information on
* valid date and time patterns.
*
* @param pattern the pattern that dates will be serialized/deserialized to/from
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.2
*/
public JasonBuilder setDateFormat(String pattern) {
// TODO(Joel): Make this fail fast if it is an invalid date format
this.datePattern = pattern;
return this;
}
/**
* Configures Jason to to serialize {@literal Date} objects according to the style value provided.
* You can call this method or {@link #setDateFormat(String)} multiple times, but only the last
* invocation will be used to decide the serialization format.
*
* Note that this style value should be one of the predefined constants in the
* {@literal DateFormat} class. See the documentation in {@link java.text.DateFormat} for more
* information on the valid style constants.
*
* @param style the predefined date style that date objects will be serialized/deserialized
* to/from
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.2
*/
public JasonBuilder setDateFormat(int style) {
this.dateStyle = style;
this.datePattern = null;
return this;
}
/**
* Configures Jason to to serialize {@literal Date} objects according to the style value provided.
* You can call this method or {@link #setDateFormat(String)} multiple times, but only the last
* invocation will be used to decide the serialization format.
*
* Note that this style value should be one of the predefined constants in the
* {@literal DateFormat} class. See the documentation in {@link java.text.DateFormat} for more
* information on the valid style constants.
*
* @param dateStyle the predefined date style that date objects will be serialized/deserialized
* to/from
* @param timeStyle the predefined style for the time portion of the date objects
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.2
*/
public JasonBuilder setDateFormat(int dateStyle, int timeStyle) {
this.dateStyle = dateStyle;
this.timeStyle = timeStyle;
this.datePattern = null;
return this;
}
/**
* Configures Jason for custom serialization or deserialization. This method combines the
* registration of an {@link TypeAdapter}, {@link InstanceCreator}, {@link JsonSerializer}, and a
* {@link JsonDeserializer}. It is best used when a single object {@literal typeAdapter} implements
all the required interfaces for custom serialization with Jason. If a type adapter was
previously registered for the specified {@literal type}, it is overwritten.
*
* This registers the type specified and no other types: you must manually register related
* types! For example, applications registering {@literal boolean.class} should also register {@literal Boolean.class}.
*
* @param type the type definition for the type adapter being registered
* @param typeAdapter This object must implement at least one of the {@link TypeAdapter},
* {@link InstanceCreator}, {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces.
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public JasonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
$Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer>
|| typeAdapter instanceof JsonDeserializer>
|| typeAdapter instanceof InstanceCreator>
|| typeAdapter instanceof TypeAdapter>);
if (typeAdapter instanceof InstanceCreator>) {
instanceCreators.put(type, (InstanceCreator) typeAdapter);
}
if (typeAdapter instanceof JsonSerializer> || typeAdapter instanceof JsonDeserializer>) {
TypeToken> typeToken = TypeToken.get(type);
factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter));
}
if (typeAdapter instanceof TypeAdapter>) {
factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter));
}
return this;
}
/**
* Register a factory for type adapters. Registering a factory is useful when the type
adapter needs to be configured based on the type of the field being processed. Jason
is designed to handle a large number of factories, so you should consider registering
them to be at par with registering an individual type adapter.
*
* @since 2.1
*/
public JasonBuilder registerTypeAdapterFactory(TypeAdapterFactory factory) {
factories.add(factory);
return this;
}
/**
* Configures Jason for custom serialization or deserialization for an inheritance type hierarchy.
* This method combines the registration of a {@link TypeAdapter}, {@link JsonSerializer} and
* a {@link JsonDeserializer}. If a type adapter was previously registered for the specified
* type hierarchy, it is overridden. If a type adapter is registered for a specific type in
* the type hierarchy, it will be invoked instead of the one registered for the type hierarchy.
*
* @param baseType the class definition for the type adapter being registered for the base class
* or interface
* @param typeAdapter This object must implement at least one of {@link TypeAdapter},
* {@link JsonSerializer} or {@link JsonDeserializer} interfaces.
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.7
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public JasonBuilder registerTypeHierarchyAdapter(Class> baseType, Object typeAdapter) {
$Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer>
|| typeAdapter instanceof JsonDeserializer>
|| typeAdapter instanceof TypeAdapter>);
if (typeAdapter instanceof JsonDeserializer || typeAdapter instanceof JsonSerializer) {
hierarchyFactories.add(0,
TreeTypeAdapter.newTypeHierarchyFactory(baseType, typeAdapter));
}
if (typeAdapter instanceof TypeAdapter>) {
factories.add(TypeAdapters.newTypeHierarchyFactory(baseType, (TypeAdapter)typeAdapter));
}
return this;
}
/**
* Section 2.4 of JSON specification disallows
* special double values (NaN, Infinity, -Infinity). However,
* Javascript
* specification (see section 4.3.20, 4.3.22, 4.3.23) allows these values as valid Javascript
* values. Moreover, most JavaScript engines will accept these special values in JSON without
* problem. So, at a practical level, it makes sense to accept these values as valid JSON even
* though JSON specification disallows them.
*
*
Jason always accepts these special values during deserialization. However, it outputs
strictly compliant JSON. Hence, if it encounters a float value {@link Float#NaN},
* {@link Float#POSITIVE_INFINITY}, {@link Float#NEGATIVE_INFINITY}, or a double value
* {@link Double#NaN}, {@link Double#POSITIVE_INFINITY}, {@link Double#NEGATIVE_INFINITY}, it
* will throw an {@link IllegalArgumentException}. This method provides a way to override the
* default behavior when you know that the JSON receiver will be able to handle these special
* values.
*
* @return a reference to this {@literal JasonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
*/
public JasonBuilder serializeSpecialFloatingPointValues() {
this.serializeSpecialFloatingPointValues = true;
return this;
}
/**
* Creates a {@link Jason} instance based on the current configuration. This method is free of
* side-effects to this {@literal JasonBuilder} instance and hence can be called multiple times.
*
* @return an instance of Jason configured with the options currently set in this builder
*/
public Jason create() {
List factories = new ArrayList();
factories.addAll(this.factories);
Collections.reverse(factories);
factories.addAll(this.hierarchyFactories);
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
return new Jason(excluder, fieldNamingPolicy, accessStrategy, instanceCreators,
serializeNulls, complexMapKeySerialization,
generateNonExecutableJson, escapeHtmlChars, prettyPrinting,
serializeSpecialFloatingPointValues, longSerializationPolicy, factories);
}
private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
List factories) {
DefaultDateTypeAdapter dateTypeAdapter;
if (datePattern != null && !"".equals(datePattern.trim())) {
dateTypeAdapter = new DefaultDateTypeAdapter(datePattern);
} else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) {
dateTypeAdapter = new DefaultDateTypeAdapter(dateStyle, timeStyle);
} else {
return;
}
factories.add(TreeTypeAdapter.newFactory(TypeToken.get(Date.class), dateTypeAdapter));
factories.add(TreeTypeAdapter.newFactory(TypeToken.get(Timestamp.class), dateTypeAdapter));
factories.add(TreeTypeAdapter.newFactory(TypeToken.get(java.sql.Date.class), dateTypeAdapter));
}
}