diff --git a/adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java b/adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java index 215b583..058231f 100644 --- a/adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java +++ b/adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java @@ -15,7 +15,8 @@ */ package com.squareup.moshi.adapters; -import com.squareup.moshi.Json; +import static com.squareup.moshi.internal.Util.jsonName; + import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.JsonDataException; import com.squareup.moshi.JsonReader; @@ -67,12 +68,7 @@ public final class EnumJsonAdapter> extends JsonAdapter { nameStrings = new String[constants.length]; for (int i = 0; i < constants.length; i++) { String constantName = constants[i].name(); - Json annotation = enumType.getField(constantName).getAnnotation(Json.class); - String name = - annotation != null && !Json.UNSET_NAME.equals(annotation.name()) - ? annotation.name() - : constantName; - nameStrings[i] = name; + nameStrings[i] = jsonName(constantName, enumType.getField(constantName)); } options = JsonReader.Options.of(nameStrings); } catch (NoSuchFieldException e) { diff --git a/examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java b/examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java index c055761..287e32c 100644 --- a/examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java +++ b/examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java @@ -17,13 +17,13 @@ package com.squareup.moshi.recipes; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.squareup.moshi.Json; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.JsonQualifier; import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonWriter; import com.squareup.moshi.Moshi; import com.squareup.moshi.Types; +import com.squareup.moshi.internal.Util; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; @@ -77,13 +77,8 @@ final class FallbackEnum { constants = enumType.getEnumConstants(); nameStrings = new String[constants.length]; for (int i = 0; i < constants.length; i++) { - T constant = constants[i]; - Json annotation = enumType.getField(constant.name()).getAnnotation(Json.class); - String name = - annotation != null && !Json.UNSET_NAME.equals(annotation.name()) - ? annotation.name() - : constant.name(); - nameStrings[i] = name; + String constantName = constants[i].name(); + nameStrings[i] = Util.jsonName(constantName, enumType.getField(constantName)); } options = JsonReader.Options.of(nameStrings); } catch (NoSuchFieldException e) { diff --git a/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt b/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt index a86ad5c..9d98142 100644 --- a/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt +++ b/kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt @@ -135,7 +135,7 @@ internal class KotlinJsonAdapter( for (binding in allBindings) { if (binding == null) continue // Skip constructor parameters that aren't properties. - writer.name(binding.name) + writer.name(binding.jsonName) binding.adapter.toJson(writer, binding.get(value)) } writer.endObject() @@ -144,8 +144,7 @@ internal class KotlinJsonAdapter( override fun toString() = "KotlinJsonAdapter(${constructor.returnType})" data class Binding( - val name: String, - val jsonName: String?, + val jsonName: String, val adapter: JsonAdapter

, val property: KProperty1, val parameter: KParameter?, @@ -264,7 +263,7 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory { if (property !is KMutableProperty1 && parameter == null) continue - val name = jsonAnnotation?.name?.takeUnless { it == Json.UNSET_NAME } ?: property.name + val jsonName = jsonAnnotation?.name?.takeUnless { it == Json.UNSET_NAME } ?: property.name val propertyType = when (val propertyTypeClassifier = property.returnType.classifier) { is KClass<*> -> { if (propertyTypeClassifier.isValue) { @@ -298,8 +297,7 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory { @Suppress("UNCHECKED_CAST") bindingsByName[property.name] = KotlinJsonAdapter.Binding( - name, - jsonAnnotation?.name?.takeUnless { it == Json.UNSET_NAME } ?: name, + jsonName, adapter, property as KProperty1, parameter, @@ -323,7 +321,7 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory { } val nonIgnoredBindings = bindings.filterNotNull() - val options = JsonReader.Options.of(*nonIgnoredBindings.map { it.name }.toTypedArray()) + val options = JsonReader.Options.of(*nonIgnoredBindings.map { it.jsonName }.toTypedArray()) return KotlinJsonAdapter(constructor, bindings, nonIgnoredBindings, options).nullSafe() } } diff --git a/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java index 41e4f01..50e7d46 100644 --- a/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java +++ b/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java @@ -15,6 +15,7 @@ */ package com.squareup.moshi; +import static com.squareup.moshi.internal.Util.jsonName; import static com.squareup.moshi.internal.Util.resolve; import com.squareup.moshi.internal.Util; @@ -147,12 +148,9 @@ final class ClassJsonAdapter extends JsonAdapter { field.setAccessible(true); // Store it using the field's name. If there was already a field with this name, fail! - String name = - jsonAnnotation != null && !Json.UNSET_NAME.equals(jsonAnnotation.name()) - ? jsonAnnotation.name() - : fieldName; - FieldBinding fieldBinding = new FieldBinding<>(name, field, adapter); - FieldBinding replaced = fieldBindings.put(name, fieldBinding); + String jsonName = jsonName(fieldName, jsonAnnotation); + FieldBinding fieldBinding = new FieldBinding<>(jsonName, field, adapter); + FieldBinding replaced = fieldBindings.put(jsonName, fieldBinding); if (replaced != null) { throw new IllegalArgumentException( "Conflicting fields:\n" diff --git a/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java b/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java index 158a371..000c858 100644 --- a/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java +++ b/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java @@ -274,12 +274,8 @@ final class StandardJsonAdapters { nameStrings = new String[constants.length]; for (int i = 0; i < constants.length; i++) { T constant = constants[i]; - Json annotation = enumType.getField(constant.name()).getAnnotation(Json.class); - String name = - annotation != null && !Json.UNSET_NAME.equals(annotation.name()) - ? annotation.name() - : constant.name(); - nameStrings[i] = name; + String constantName = constant.name(); + nameStrings[i] = Util.jsonName(constantName, enumType.getField(constantName)); } options = JsonReader.Options.of(nameStrings); } catch (NoSuchFieldException e) { diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.java b/moshi/src/main/java/com/squareup/moshi/internal/Util.java index b517c98..d8edb50 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.java +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.java @@ -19,6 +19,7 @@ import static com.squareup.moshi.Types.arrayOf; import static com.squareup.moshi.Types.subtypeOf; import static com.squareup.moshi.Types.supertypeOf; +import com.squareup.moshi.Json; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.JsonClass; import com.squareup.moshi.JsonDataException; @@ -95,6 +96,16 @@ public final class Util { private Util() {} + public static String jsonName(String declaredName, AnnotatedElement element) { + return jsonName(declaredName, element.getAnnotation(Json.class)); + } + + public static String jsonName(String declaredName, @Nullable Json annotation) { + if (annotation == null) return declaredName; + String annotationName = annotation.name(); + return Json.UNSET_NAME.equals(annotationName) ? declaredName : annotationName; + } + public static boolean typesMatch(Type pattern, Type candidate) { // TODO: permit raw types (like Set.class) to match non-raw candidates (like Set). return Types.equals(pattern, candidate); diff --git a/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java b/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java index 4d74711..5c65ceb 100644 --- a/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java +++ b/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java @@ -15,6 +15,7 @@ */ package com.squareup.moshi; +import static com.squareup.moshi.internal.Util.rethrowCause; import static java.lang.invoke.MethodType.methodType; import com.squareup.moshi.internal.Util; @@ -25,9 +26,8 @@ import java.lang.invoke.MethodHandles; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.RecordComponent; -import java.util.Collections; +import java.lang.reflect.Type; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -39,80 +39,81 @@ import java.util.Set; final class RecordJsonAdapter extends JsonAdapter { static final JsonAdapter.Factory FACTORY = - (type, annotations, moshi) -> { - if (!annotations.isEmpty()) { - return null; - } + new Factory() { + @Override + public JsonAdapter create( + Type type, Set annotations, Moshi moshi) { + if (!annotations.isEmpty()) { + return null; + } - if (!(type instanceof Class) && !(type instanceof ParameterizedType)) { - return null; - } + if (!(type instanceof Class) && !(type instanceof ParameterizedType)) { + return null; + } - var rawType = Types.getRawType(type); - if (!rawType.isRecord()) { - return null; - } + var rawType = Types.getRawType(type); + if (!rawType.isRecord()) { + return null; + } - var components = rawType.getRecordComponents(); - var bindings = new LinkedHashMap>(); - var componentRawTypes = new Class[components.length]; - var lookup = MethodHandles.lookup(); - for (int i = 0, componentsLength = components.length; i < componentsLength; i++) { - RecordComponent component = components[i]; - componentRawTypes[i] = component.getType(); - var name = component.getName(); - var componentType = Util.resolve(type, rawType, component.getGenericType()); - var jsonName = name; - Set qualifiers = null; - for (var annotation : component.getDeclaredAnnotations()) { - if (annotation instanceof Json jsonAnnotation) { - var annotationName = jsonAnnotation.name(); - if (!Json.UNSET_NAME.equals(annotationName)) { - jsonName = jsonAnnotation.name(); - } - } else { - if (annotation.annotationType().isAnnotationPresent(JsonQualifier.class)) { - if (qualifiers == null) { - qualifiers = new LinkedHashSet<>(); - } - qualifiers.add(annotation); - } + var components = rawType.getRecordComponents(); + var bindings = new LinkedHashMap>(); + var componentRawTypes = new Class[components.length]; + var lookup = MethodHandles.lookup(); + for (int i = 0, componentsLength = components.length; i < componentsLength; i++) { + RecordComponent component = components[i]; + componentRawTypes[i] = component.getType(); + ComponentBinding componentBinding = + createComponentBinding(type, rawType, moshi, lookup, component); + var replaced = bindings.put(componentBinding.jsonName, componentBinding); + if (replaced != null) { + throw new IllegalArgumentException( + "Conflicting components:\n" + + " " + + replaced.componentName + + "\n" + + " " + + componentBinding.componentName); } } - if (qualifiers == null) { - qualifiers = Collections.emptySet(); + + MethodHandle constructor; + try { + constructor = + lookup.findConstructor(rawType, methodType(void.class, componentRawTypes)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new AssertionError(e); } + + return new RecordJsonAdapter<>(constructor, rawType.getSimpleName(), bindings).nullSafe(); + } + + private static ComponentBinding createComponentBinding( + Type type, + Class rawType, + Moshi moshi, + MethodHandles.Lookup lookup, + RecordComponent component) { + var componentName = component.getName(); + var jsonName = Util.jsonName(componentName, component); + + var componentType = Util.resolve(type, rawType, component.getGenericType()); + Set qualifiers = Util.jsonAnnotations(component); var adapter = moshi.adapter(componentType, qualifiers); + MethodHandle accessor; try { accessor = lookup.unreflect(component.getAccessor()); } catch (IllegalAccessException e) { throw new AssertionError(e); } - var componentBinding = new ComponentBinding<>(name, jsonName, adapter, accessor); - var replaced = bindings.put(jsonName, componentBinding); - if (replaced != null) { - throw new IllegalArgumentException( - "Conflicting components:\n" - + " " - + replaced.name - + "\n" - + " " - + componentBinding.name); - } - } - MethodHandle constructor; - try { - constructor = lookup.findConstructor(rawType, methodType(void.class, componentRawTypes)); - } catch (NoSuchMethodException | IllegalAccessException e) { - throw new AssertionError(e); + return new ComponentBinding<>(componentName, jsonName, adapter, accessor); } - return new RecordJsonAdapter<>(constructor, rawType.getSimpleName(), bindings).nullSafe(); }; private static record ComponentBinding( - String name, String jsonName, JsonAdapter adapter, MethodHandle accessor) {} + String componentName, String jsonName, JsonAdapter adapter, MethodHandle accessor) {} private final String targetClass; private final MethodHandle constructor; @@ -146,23 +147,17 @@ final class RecordJsonAdapter extends JsonAdapter { reader.skipValue(); continue; } - var result = componentBindingsArray[index].adapter.fromJson(reader); - resultsArray[index] = result; + resultsArray[index] = componentBindingsArray[index].adapter.fromJson(reader); } reader.endObject(); try { //noinspection unchecked return (T) constructor.invokeWithArguments(resultsArray); + } catch (InvocationTargetException e) { + throw rethrowCause(e); } catch (Throwable e) { - if (e instanceof InvocationTargetException ite) { - Throwable cause = ite.getCause(); - if (cause instanceof RuntimeException) throw (RuntimeException) cause; - if (cause instanceof Error) throw (Error) cause; - throw new RuntimeException(cause); - } else { - throw new AssertionError(e); - } + throw new AssertionError(e); } } @@ -175,15 +170,10 @@ final class RecordJsonAdapter extends JsonAdapter { Object componentValue; try { componentValue = binding.accessor.invoke(value); + } catch (InvocationTargetException e) { + throw Util.rethrowCause(e); } catch (Throwable e) { - if (e instanceof InvocationTargetException ite) { - Throwable cause = ite.getCause(); - if (cause instanceof RuntimeException) throw (RuntimeException) cause; - if (cause instanceof Error) throw (Error) cause; - throw new RuntimeException(cause); - } else { - throw new AssertionError(e); - } + throw new AssertionError(e); } binding.adapter.toJson(writer, componentValue); }