Skip to content

Commit

Permalink
Merge branch 'main' into feature/issue#2436_enhancement
Browse files Browse the repository at this point in the history
  • Loading branch information
sachinp97 authored Aug 25, 2023
2 parents 0218548 + 2432cf7 commit 0701f89
Show file tree
Hide file tree
Showing 26 changed files with 1,382 additions and 235 deletions.
96 changes: 56 additions & 40 deletions gson/src/main/java/com/google/gson/Gson.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.gson;

import com.google.gson.annotations.JsonAdapter;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.GsonBuildConfig;
Expand Down Expand Up @@ -604,42 +605,50 @@ public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
* adapter that does a little bit of work but then delegates further processing to the Gson
* default type adapter. Here is an example:
* <p>Let's say we want to write a type adapter that counts the number of objects being read
* from or written to JSON. We can achieve this by writing a type adapter factory that uses
* the <code>getDelegateAdapter</code> method:
* <pre> {@code
* class StatsTypeAdapterFactory implements TypeAdapterFactory {
* public int numReads = 0;
* public int numWrites = 0;
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
* final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
* return new TypeAdapter<T>() {
* public void write(JsonWriter out, T value) throws IOException {
* ++numWrites;
* delegate.write(out, value);
* }
* public T read(JsonReader in) throws IOException {
* ++numReads;
* return delegate.read(in);
* }
* };
* }
* }
* } </pre>
* This factory can now be used like this:
* <pre> {@code
* StatsTypeAdapterFactory stats = new StatsTypeAdapterFactory();
* Gson gson = new GsonBuilder().registerTypeAdapterFactory(stats).create();
* // Call gson.toJson() and fromJson methods on objects
* System.out.println("Num JSON reads" + stats.numReads);
* System.out.println("Num JSON writes" + stats.numWrites);
* }</pre>
* Note that this call will skip all factories registered before {@code skipPast}. In case of
* multiple TypeAdapterFactories registered it is up to the caller of this function to insure
* that the order of registration does not prevent this method from reaching a factory they
* would expect to reply from this call.
* Note that since you can not override type adapter factories for String and Java primitive
* types, our stats factory will not count the number of String or primitives that will be
* read or written.
* from or written to JSON. We can achieve this by writing a type adapter factory that uses
* the <code>getDelegateAdapter</code> method:
* <pre>{@code
* class StatsTypeAdapterFactory implements TypeAdapterFactory {
* public int numReads = 0;
* public int numWrites = 0;
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
* final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
* return new TypeAdapter<T>() {
* public void write(JsonWriter out, T value) throws IOException {
* ++numWrites;
* delegate.write(out, value);
* }
* public T read(JsonReader in) throws IOException {
* ++numReads;
* return delegate.read(in);
* }
* };
* }
* }
* }</pre>
* This factory can now be used like this:
* <pre>{@code
* StatsTypeAdapterFactory stats = new StatsTypeAdapterFactory();
* Gson gson = new GsonBuilder().registerTypeAdapterFactory(stats).create();
* // Call gson.toJson() and fromJson methods on objects
* System.out.println("Num JSON reads: " + stats.numReads);
* System.out.println("Num JSON writes: " + stats.numWrites);
* }</pre>
* Note that this call will skip all factories registered before {@code skipPast}. In case of
* multiple TypeAdapterFactories registered it is up to the caller of this function to insure
* that the order of registration does not prevent this method from reaching a factory they
* would expect to reply from this call.
* Note that since you can not override the type adapter factories for some types, see
* {@link GsonBuilder#registerTypeAdapter(Type, Object)}, our stats factory will not count
* the number of instances of those types that will be read or written.
*
* <p>If {@code skipPast} is a factory which has neither been registered on the {@link GsonBuilder}
* nor specified with the {@link JsonAdapter @JsonAdapter} annotation on a class, then this
* method behaves as if {@link #getAdapter(TypeToken)} had been called. This also means that
* for fields with {@code @JsonAdapter} annotation this method behaves normally like {@code getAdapter}
* (except for corner cases where a custom {@link InstanceCreator} is used to create an
* instance of the factory).
*
* @param skipPast The type adapter factory that needs to be skipped while searching for
* a matching type adapter. In most cases, you should just pass <i>this</i> (the type adapter
* factory from where {@code getDelegateAdapter} method is being invoked).
Expand All @@ -648,9 +657,10 @@ public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
* @since 2.2
*/
public <T> TypeAdapter<T> getDelegateAdapter(TypeAdapterFactory skipPast, TypeToken<T> type) {
// Hack. If the skipPast factory isn't registered, assume the factory is being requested via
// our @JsonAdapter annotation.
if (!factories.contains(skipPast)) {
Objects.requireNonNull(skipPast, "skipPast must not be null");
Objects.requireNonNull(type, "type must not be null");

if (jsonAdapterFactory.isClassJsonAdapterFactory(type, skipPast)) {
skipPast = jsonAdapterFactory;
}

Expand All @@ -668,7 +678,13 @@ public <T> TypeAdapter<T> getDelegateAdapter(TypeAdapterFactory skipPast, TypeTo
return candidate;
}
}
throw new IllegalArgumentException("GSON cannot serialize " + type);

if (skipPastFound) {
throw new IllegalArgumentException("GSON cannot serialize " + type);
} else {
// Probably a factory from @JsonAdapter on a field
return getAdapter(type);
}
}

/**
Expand Down
6 changes: 4 additions & 2 deletions gson/src/main/java/com/google/gson/InstanceCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
* </pre>
*
* <p>Note that it does not matter what the fields of the created instance contain since Gson will
* overwrite them with the deserialized values specified in Json. You should also ensure that a
* overwrite them with the deserialized values specified in JSON. You should also ensure that a
* <i>new</i> object is returned, not a common object since its fields will be overwritten.
* The developer will need to register {@code IdInstanceCreator} with Gson as follows:</p>
*
Expand All @@ -73,6 +73,8 @@
*
* @param <T> the type of object that will be created by this implementation.
*
* @see GsonBuilder#registerTypeAdapter(Type, Object)
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
Expand All @@ -81,7 +83,7 @@ public interface InstanceCreator<T> {
/**
* Gson invokes this call-back method during deserialization to create an instance of the
* specified type. The fields of the returned instance are overwritten with the data present
* in the Json. Since the prior contents of the object are destroyed and overwritten, do not
* in the JSON. Since the prior contents of the object are destroyed and overwritten, do not
* return an instance that is useful elsewhere. In particular, do not return a common instance,
* always use {@code new} to create a new instance.
*
Expand Down
44 changes: 35 additions & 9 deletions gson/src/main/java/com/google/gson/annotations/JsonAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package com.google.gson.annotations;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapter;
Expand All @@ -35,20 +37,22 @@
* &#64;JsonAdapter(UserJsonAdapter.class)
* public class User {
* public final String firstName, lastName;
*
* private User(String firstName, String lastName) {
* this.firstName = firstName;
* this.lastName = lastName;
* }
* }
*
* public class UserJsonAdapter extends TypeAdapter&lt;User&gt; {
* &#64;Override public void write(JsonWriter out, User user) throws IOException {
* // implement write: combine firstName and lastName into name
* out.beginObject();
* out.name("name");
* out.value(user.firstName + " " + user.lastName);
* out.endObject();
* // implement the write method
* }
*
* &#64;Override public User read(JsonReader in) throws IOException {
* // implement read: split name into firstName and lastName
* in.beginObject();
Expand All @@ -60,30 +64,46 @@
* }
* </pre>
*
* Since User class specified UserJsonAdapter.class in &#64;JsonAdapter annotation, it
* will automatically be invoked to serialize/deserialize User instances.
* Since {@code User} class specified {@code UserJsonAdapter.class} in {@code @JsonAdapter}
* annotation, it will automatically be invoked to serialize/deserialize {@code User} instances.
*
* <p> Here is an example of how to apply this annotation to a field.
* <p>Here is an example of how to apply this annotation to a field.
* <pre>
* private static final class Gadget {
* &#64;JsonAdapter(UserJsonAdapter2.class)
* &#64;JsonAdapter(UserJsonAdapter.class)
* final User user;
*
* Gadget(User user) {
* this.user = user;
* }
* }
* </pre>
*
* It's possible to specify different type adapters on a field, that
* field's type, and in the {@link com.google.gson.GsonBuilder}. Field
* annotations take precedence over {@code GsonBuilder}-registered type
* field's type, and in the {@link GsonBuilder}. Field annotations
* take precedence over {@code GsonBuilder}-registered type
* adapters, which in turn take precedence over annotated types.
*
* <p>The class referenced by this annotation must be either a {@link
* TypeAdapter} or a {@link TypeAdapterFactory}, or must implement one
* or both of {@link JsonDeserializer} or {@link JsonSerializer}.
* Using {@link TypeAdapterFactory} makes it possible to delegate
* to the enclosing {@link Gson} instance.
* to the enclosing {@link Gson} instance. By default the specified
* adapter will not be called for {@code null} values; set {@link #nullSafe()}
* to {@code false} to let the adapter handle {@code null} values itself.
*
* <p>The type adapter is created in the same way Gson creates instances of
* custom classes during deserialization, that means:
* <ol>
* <li>If a custom {@link InstanceCreator} has been registered for the
* adapter class, it will be used to create the instance
* <li>Otherwise, if the adapter class has a no-args constructor
* (regardless of which visibility), it will be invoked to create
* the instance
* <li>Otherwise, JDK {@code Unsafe} will be used to create the instance;
* see {@link GsonBuilder#disableJdkUnsafe()} for the unexpected
* side-effects this might have
* </ol>
*
* <p>{@code Gson} instances might cache the adapter they create for
* a {@code @JsonAdapter} annotation. It is not guaranteed that a new
Expand All @@ -104,7 +124,13 @@
/** Either a {@link TypeAdapter} or {@link TypeAdapterFactory}, or one or both of {@link JsonDeserializer} or {@link JsonSerializer}. */
Class<?> value();

/** false, to be able to handle {@code null} values within the adapter, default value is true. */
/**
* Whether the adapter referenced by {@link #value()} should be made {@linkplain TypeAdapter#nullSafe() null-safe}.
*
* <p>If {@code true} (the default), it will be made null-safe and Gson will handle {@code null} Java objects
* on serialization and JSON {@code null} on deserialization without calling the adapter. If {@code false},
* the adapter will have to handle the {@code null} values.
*/
boolean nullSafe() default true;

}
Loading

0 comments on commit 0701f89

Please sign in to comment.