-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add strategies for unknown and missing fields
- Loading branch information
1 parent
c34b9ff
commit 140eda7
Showing
14 changed files
with
997 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
gson/src/main/java/com/google/gson/MissingFieldValueStrategy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package com.google.gson; | ||
|
||
import com.google.gson.internal.reflect.ReflectionHelper; | ||
import com.google.gson.reflect.TypeToken; | ||
import java.lang.reflect.Field; | ||
|
||
/** | ||
* A strategy defining how to handle missing field values during reflection-based deserialization. | ||
* | ||
* @see GsonBuilder#setMissingFieldValueStrategy(MissingFieldValueStrategy) | ||
* @since $next-version$ | ||
*/ | ||
public interface MissingFieldValueStrategy { | ||
/** | ||
* This strategy does nothing when a missing field is detected, it preserves the initial field | ||
* value, if any. | ||
* | ||
* <p>This is the default missing field value strategy. | ||
*/ | ||
MissingFieldValueStrategy DO_NOTHING = new MissingFieldValueStrategy() { | ||
@Override | ||
public Object handleMissingField(TypeToken<?> declaringType, Object instance, Field field, TypeToken<?> resolvedFieldType) { | ||
// Preserve initial field value | ||
return null; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "MissingFieldValueStrategy.DO_NOTHING"; | ||
} | ||
}; | ||
|
||
/** | ||
* This strategy throws an exception when a missing field is detected. | ||
*/ | ||
MissingFieldValueStrategy THROW_EXCEPTION = new MissingFieldValueStrategy() { | ||
@Override | ||
public Object handleMissingField(TypeToken<?> declaringType, Object instance, Field field, TypeToken<?> resolvedFieldType) { | ||
// TODO: Proper exception | ||
throw new RuntimeException("Missing value for field '" + ReflectionHelper.fieldToString(field) + "'"); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "MissingFieldValueStrategy.THROW_EXCEPTION"; | ||
} | ||
}; | ||
|
||
/** | ||
* Called when a missing field value is detected. Implementations can either throw an exception or | ||
* return a default value. | ||
* | ||
* <p>Returning {@code null} will keep the initial field value, if any. For example when returning | ||
* {@code null} for the field {@code String f = "default"}, the field will still have the value | ||
* {@code "default"} afterwards (assuming the constructor of the class was called, see also | ||
* {@link GsonBuilder#disableJdkUnsafe()}). The type of the returned value has to match the | ||
* type of the field, no narrowing or widening numeric conversion is performed. | ||
* | ||
* <p>The {@code instance} represents an instance of the declaring type with the so far already | ||
* deserialized fields. It is intended to be used for looking up existing field values to derive | ||
* the missing field value from them. Manipulating {@code instance} in any way is not recommended.<br> | ||
* For Record classes (Java 16 feature) the {@code instance} is {@code null}. | ||
* | ||
* <p>{@code resolvedFieldType} is the type of the field with type variables being resolved, if | ||
* possible. For example if {@code class MyClass<T>} has a field {@code T myField} and | ||
* {@code MyClass<String>} is deserialized, then {@code resolvedFieldType} will be {@code String}. | ||
* | ||
* @param declaringType type declaring the field | ||
* @param instance instance of the declaring type, {@code null} for Record classes | ||
* @param field field whose value is missing | ||
* @param resolvedFieldType resolved type of the field | ||
* @return the field value, or {@code null} | ||
*/ | ||
// TODO: Should this really expose `instance`? Only use case would be to derive value from other fields | ||
// but besides that user should not directly manipulate `instance` but return new value instead | ||
Object handleMissingField(TypeToken<?> declaringType, Object instance, Field field, TypeToken<?> resolvedFieldType); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
gson/src/main/java/com/google/gson/UnknownFieldStrategy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package com.google.gson; | ||
|
||
import com.google.gson.reflect.TypeToken; | ||
import com.google.gson.stream.JsonReader; | ||
import java.io.IOException; | ||
|
||
/** | ||
* A strategy defining how to handle unknown fields during reflection-based deserialization. | ||
* | ||
* @see GsonBuilder#setUnknownFieldStrategy(UnknownFieldStrategy) | ||
* @since $next-version$ | ||
*/ | ||
public interface UnknownFieldStrategy { | ||
/** | ||
* This strategy ignores the unknown field. | ||
* | ||
* <p>This is the default unknown field strategy. | ||
*/ | ||
UnknownFieldStrategy IGNORE = new UnknownFieldStrategy() { | ||
@Override | ||
public void handleUnknownField(TypeToken<?> declaringType, Object instance, String fieldName, | ||
JsonReader jsonReader, Gson gson) throws IOException { | ||
jsonReader.skipValue(); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "UnknownFieldStrategy.IGNORE"; | ||
} | ||
}; | ||
|
||
/** | ||
* This strategy throws an exception when an unknown field is encountered. | ||
* | ||
* <p><b>Note:</b> Be careful when using this strategy; while it might sound tempting | ||
* to strictly validate that the JSON data matches the expected format, this strategy | ||
* makes it difficult to add new fields to the JSON structure in a backward compatible way. | ||
* Usually it suffices to use only {@link MissingFieldValueStrategy#THROW_EXCEPTION} for | ||
* validation and to ignore unknown fields. | ||
*/ | ||
UnknownFieldStrategy THROW_EXCEPTION = new UnknownFieldStrategy() { | ||
@Override | ||
public void handleUnknownField(TypeToken<?> declaringType, Object instance, String fieldName, | ||
JsonReader jsonReader, Gson gson) throws IOException { | ||
// TODO: Proper exception | ||
throw new RuntimeException("Unknown field '" + fieldName + "' for " + declaringType.getRawType() + " at path " + jsonReader.getPath()); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "UnknownFieldStrategy.THROW_EXCEPTION"; | ||
} | ||
}; | ||
|
||
/** | ||
* Called when an unknown field is encountered. Implementations can throw an exception, | ||
* store the field value in {@code instance} or ignore the unknown field. | ||
* | ||
* <p>The {@code jsonReader} is positioned to read the value of the unknown field. If an | ||
* implementation of this method does not throw an exception it must consume the value, either | ||
* by reading it with methods like {@link JsonReader#nextString()} (possibly after peeking | ||
* at the value type first), or by skipping it with {@link JsonReader#skipValue()}.<br> | ||
* The {@code gson} object can be used to read from the {@code jsonReader}. It is the same | ||
* instance which was originally used to perform the deserialization. | ||
* | ||
* <p>The {@code instance} represents an instance of the declaring type with the so far already | ||
* deserialized fields. It can be used to store the value of the unknown field, for example | ||
* if it declares a {@code transient Map<String, Object>} field for all unknown values.<br> | ||
* For Record classes (Java 16 feature) the {@code instance} is {@code null}. | ||
* | ||
* @param declaringType type declaring the field | ||
* @param instance instance of the declaring type, {@code null} for Record classes | ||
* @param fieldName name of the unknown field | ||
* @param jsonReader reader to be used to read or skip the field value | ||
* @param gson {@code Gson} instance which can be used to read the field value from {@code jsonReader} | ||
* @throws IOException if reading or skipping the field value fails | ||
*/ | ||
void handleUnknownField(TypeToken<?> declaringType, Object instance, String fieldName, JsonReader jsonReader, Gson gson) throws IOException; | ||
} |
Oops, something went wrong.