-
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.
Move enum and
JsonElement
adapter classes to separate class files (#…
…2727) * Move enum and `JsonElement` adapter classes to separate class files Their implementation is complex enough to probably justify having them in their own class files, to make maintaining them easier. For the users this should not be noticeable since these classes are still in the Gson internal package. * Add back `TypeAdapters` constants used by external projects Note: `JSON_ELEMENT_FACTORY` seems to be rarely used, therefore this has not been added back. * Add back `TypeAdapters.JSON_ELEMENT_FACTORY`
- Loading branch information
1 parent
9615c8d
commit 93596da
Showing
6 changed files
with
330 additions
and
227 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
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
112 changes: 112 additions & 0 deletions
112
gson/src/main/java/com/google/gson/internal/bind/EnumTypeAdapter.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,112 @@ | ||
/* | ||
* Copyright (C) 2024 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 com.google.gson.internal.bind; | ||
|
||
import com.google.gson.Gson; | ||
import com.google.gson.TypeAdapter; | ||
import com.google.gson.TypeAdapterFactory; | ||
import com.google.gson.annotations.SerializedName; | ||
import com.google.gson.reflect.TypeToken; | ||
import com.google.gson.stream.JsonReader; | ||
import com.google.gson.stream.JsonToken; | ||
import com.google.gson.stream.JsonWriter; | ||
import java.io.IOException; | ||
import java.lang.reflect.AccessibleObject; | ||
import java.lang.reflect.Field; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
/** Adapter for enum classes (but not for the base class {@code java.lang.Enum}). */ | ||
public class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> { | ||
public static final TypeAdapterFactory FACTORY = | ||
new TypeAdapterFactory() { | ||
@Override | ||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { | ||
Class<? super T> rawType = typeToken.getRawType(); | ||
if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) { | ||
return null; | ||
} | ||
if (!rawType.isEnum()) { | ||
rawType = rawType.getSuperclass(); // handle anonymous subclasses | ||
} | ||
@SuppressWarnings({"rawtypes", "unchecked"}) | ||
TypeAdapter<T> adapter = (TypeAdapter<T>) new EnumTypeAdapter(rawType); | ||
return adapter; | ||
} | ||
}; | ||
|
||
private final Map<String, T> nameToConstant = new HashMap<>(); | ||
private final Map<String, T> stringToConstant = new HashMap<>(); | ||
private final Map<T, String> constantToName = new HashMap<>(); | ||
|
||
private EnumTypeAdapter(final Class<T> classOfT) { | ||
try { | ||
// Uses reflection to find enum constants to work around name mismatches for obfuscated | ||
// classes | ||
Field[] fields = classOfT.getDeclaredFields(); | ||
ArrayList<Field> constantFieldsList = new ArrayList<>(fields.length); | ||
for (Field f : fields) { | ||
if (f.isEnumConstant()) { | ||
constantFieldsList.add(f); | ||
} | ||
} | ||
|
||
Field[] constantFields = constantFieldsList.toArray(new Field[0]); | ||
AccessibleObject.setAccessible(constantFields, true); | ||
|
||
for (Field constantField : constantFields) { | ||
@SuppressWarnings("unchecked") | ||
T constant = (T) constantField.get(null); | ||
String name = constant.name(); | ||
String toStringVal = constant.toString(); | ||
|
||
SerializedName annotation = constantField.getAnnotation(SerializedName.class); | ||
if (annotation != null) { | ||
name = annotation.value(); | ||
for (String alternate : annotation.alternate()) { | ||
nameToConstant.put(alternate, constant); | ||
} | ||
} | ||
nameToConstant.put(name, constant); | ||
stringToConstant.put(toStringVal, constant); | ||
constantToName.put(constant, name); | ||
} | ||
} catch (IllegalAccessException e) { | ||
// IllegalAccessException should be impossible due to the `setAccessible` call above; | ||
// and even that should probably not fail since enum constants are implicitly public | ||
throw new AssertionError(e); | ||
} | ||
} | ||
|
||
@Override | ||
public T read(JsonReader in) throws IOException { | ||
if (in.peek() == JsonToken.NULL) { | ||
in.nextNull(); | ||
return null; | ||
} | ||
String key = in.nextString(); | ||
T constant = nameToConstant.get(key); | ||
// Note: If none of the approaches find the constant, this returns null | ||
return (constant == null) ? stringToConstant.get(key) : constant; | ||
} | ||
|
||
@Override | ||
public void write(JsonWriter out, T value) throws IOException { | ||
out.value(value == null ? null : constantToName.get(value)); | ||
} | ||
} |
175 changes: 175 additions & 0 deletions
175
gson/src/main/java/com/google/gson/internal/bind/JsonElementTypeAdapter.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,175 @@ | ||
/* | ||
* Copyright (C) 2024 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 com.google.gson.internal.bind; | ||
|
||
import com.google.gson.JsonArray; | ||
import com.google.gson.JsonElement; | ||
import com.google.gson.JsonNull; | ||
import com.google.gson.JsonObject; | ||
import com.google.gson.JsonPrimitive; | ||
import com.google.gson.TypeAdapter; | ||
import com.google.gson.TypeAdapterFactory; | ||
import com.google.gson.internal.LazilyParsedNumber; | ||
import com.google.gson.stream.JsonReader; | ||
import com.google.gson.stream.JsonToken; | ||
import com.google.gson.stream.JsonWriter; | ||
import java.io.IOException; | ||
import java.util.ArrayDeque; | ||
import java.util.Deque; | ||
import java.util.Map; | ||
|
||
/** Adapter for {@link JsonElement} and subclasses. */ | ||
public class JsonElementTypeAdapter extends TypeAdapter<JsonElement> { | ||
public static final JsonElementTypeAdapter ADAPTER = new JsonElementTypeAdapter(); | ||
|
||
public static final TypeAdapterFactory FACTORY = | ||
TypeAdapters.newTypeHierarchyFactory(JsonElement.class, ADAPTER); | ||
|
||
private JsonElementTypeAdapter() {} | ||
|
||
/** | ||
* Tries to begin reading a JSON array or JSON object, returning {@code null} if the next element | ||
* is neither of those. | ||
*/ | ||
private JsonElement tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException { | ||
switch (peeked) { | ||
case BEGIN_ARRAY: | ||
in.beginArray(); | ||
return new JsonArray(); | ||
case BEGIN_OBJECT: | ||
in.beginObject(); | ||
return new JsonObject(); | ||
default: | ||
return null; | ||
} | ||
} | ||
|
||
/** Reads a {@link JsonElement} which cannot have any nested elements */ | ||
private JsonElement readTerminal(JsonReader in, JsonToken peeked) throws IOException { | ||
switch (peeked) { | ||
case STRING: | ||
return new JsonPrimitive(in.nextString()); | ||
case NUMBER: | ||
String number = in.nextString(); | ||
return new JsonPrimitive(new LazilyParsedNumber(number)); | ||
case BOOLEAN: | ||
return new JsonPrimitive(in.nextBoolean()); | ||
case NULL: | ||
in.nextNull(); | ||
return JsonNull.INSTANCE; | ||
default: | ||
// When read(JsonReader) is called with JsonReader in invalid state | ||
throw new IllegalStateException("Unexpected token: " + peeked); | ||
} | ||
} | ||
|
||
@Override | ||
public JsonElement read(JsonReader in) throws IOException { | ||
// Optimization if value already exists as JsonElement | ||
if (in instanceof JsonTreeReader) { | ||
return ((JsonTreeReader) in).nextJsonElement(); | ||
} | ||
|
||
// Either JsonArray or JsonObject | ||
JsonElement current; | ||
JsonToken peeked = in.peek(); | ||
|
||
current = tryBeginNesting(in, peeked); | ||
if (current == null) { | ||
return readTerminal(in, peeked); | ||
} | ||
|
||
Deque<JsonElement> stack = new ArrayDeque<>(); | ||
|
||
while (true) { | ||
while (in.hasNext()) { | ||
String name = null; | ||
// Name is only used for JSON object members | ||
if (current instanceof JsonObject) { | ||
name = in.nextName(); | ||
} | ||
|
||
peeked = in.peek(); | ||
JsonElement value = tryBeginNesting(in, peeked); | ||
boolean isNesting = value != null; | ||
|
||
if (value == null) { | ||
value = readTerminal(in, peeked); | ||
} | ||
|
||
if (current instanceof JsonArray) { | ||
((JsonArray) current).add(value); | ||
} else { | ||
((JsonObject) current).add(name, value); | ||
} | ||
|
||
if (isNesting) { | ||
stack.addLast(current); | ||
current = value; | ||
} | ||
} | ||
|
||
// End current element | ||
if (current instanceof JsonArray) { | ||
in.endArray(); | ||
} else { | ||
in.endObject(); | ||
} | ||
|
||
if (stack.isEmpty()) { | ||
return current; | ||
} else { | ||
// Continue with enclosing element | ||
current = stack.removeLast(); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void write(JsonWriter out, JsonElement value) throws IOException { | ||
if (value == null || value.isJsonNull()) { | ||
out.nullValue(); | ||
} else if (value.isJsonPrimitive()) { | ||
JsonPrimitive primitive = value.getAsJsonPrimitive(); | ||
if (primitive.isNumber()) { | ||
out.value(primitive.getAsNumber()); | ||
} else if (primitive.isBoolean()) { | ||
out.value(primitive.getAsBoolean()); | ||
} else { | ||
out.value(primitive.getAsString()); | ||
} | ||
|
||
} else if (value.isJsonArray()) { | ||
out.beginArray(); | ||
for (JsonElement e : value.getAsJsonArray()) { | ||
write(out, e); | ||
} | ||
out.endArray(); | ||
|
||
} else if (value.isJsonObject()) { | ||
out.beginObject(); | ||
for (Map.Entry<String, JsonElement> e : value.getAsJsonObject().entrySet()) { | ||
out.name(e.getKey()); | ||
write(out, e.getValue()); | ||
} | ||
out.endObject(); | ||
|
||
} else { | ||
throw new IllegalArgumentException("Couldn't write " + value.getClass()); | ||
} | ||
} | ||
} |
Oops, something went wrong.