Skip to content

Commit

Permalink
Fix a crash when combining generics and records
Browse files Browse the repository at this point in the history
Without the fix the test fails like so:

    No JsonAdapter for T (with no annotations)
    for T
    for java.util.List<T>
    for com.squareup.moshi.records.RecordsTest$IndirectGenerics<java.lang.Long>
    for class com.squareup.moshi.records.RecordsTest$HasIndirectGenerics
    java.lang.IllegalArgumentException: No JsonAdapter for T (with no annotations)
    for T
    for java.util.List<T>
    for com.squareup.moshi.records.RecordsTest$IndirectGenerics<java.lang.Long>
    for class com.squareup.moshi.records.RecordsTest$HasIndirectGenerics
      at com.squareup.moshi.Moshi$LookupChain.exceptionWithLookupStack(Moshi.java:389)
      at com.squareup.moshi.Moshi.adapter(Moshi.java:158)
      at com.squareup.moshi.Moshi.adapter(Moshi.java:106)
      at com.squareup.moshi.Moshi.adapter(Moshi.java:75)
  • Loading branch information
swankjesse committed Dec 4, 2021
1 parent 751e821 commit e45655b
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 39 deletions.
Expand Up @@ -15,9 +15,6 @@
*/
package com.squareup.moshi.records;

import static com.google.common.truth.Truth.assertThat;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import com.squareup.moshi.FromJson;
import com.squareup.moshi.Json;
import com.squareup.moshi.JsonQualifier;
Expand All @@ -33,6 +30,9 @@
import java.util.Set;
import org.junit.Test;

import static com.google.common.truth.Truth.assertThat;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

public final class RecordsTest {

private final Moshi moshi = new Moshi.Builder().build();
Expand Down Expand Up @@ -156,6 +156,22 @@ public void genericBoundedRecord() throws IOException {
assertThat(adapter.fromJson("{\"value\":4}")).isEqualTo(new GenericBoundedRecord<>(4));
}

@Test
public void indirectGenerics() throws IOException {
var value = new HasIndirectGenerics(
new IndirectGenerics<>(1L, List.of(2L, 3L, 4L), Map.of("five", 5L)));
var jsonAdapter = moshi.adapter(HasIndirectGenerics.class);
var json = "{\"value\":{\"single\":1,\"list\":[2,3,4],\"map\":{\"five\":5}}}";
assertThat(jsonAdapter.toJson(value)).isEqualTo(json);
assertThat(jsonAdapter.fromJson(json)).isEqualTo(value);
}

public static record IndirectGenerics<T>(T single, List<T> list, Map<String, T> map) {
}

public static record HasIndirectGenerics(IndirectGenerics<Long> value) {
}

@Test
public void qualifiedValues() throws IOException {
var adapter = moshi.newBuilder().add(new ColorAdapter()).build().adapter(QualifiedValues.class);
Expand Down
8 changes: 4 additions & 4 deletions moshi/src/main/java/com/squareup/moshi/internal/Util.java
Expand Up @@ -15,10 +15,6 @@
*/
package com.squareup.moshi.internal;

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.JsonAdapter;
import com.squareup.moshi.JsonClass;
import com.squareup.moshi.JsonDataException;
Expand Down Expand Up @@ -46,6 +42,10 @@
import java.util.Set;
import javax.annotation.Nullable;

import static com.squareup.moshi.Types.arrayOf;
import static com.squareup.moshi.Types.subtypeOf;
import static com.squareup.moshi.Types.supertypeOf;

public final class Util {
public static final Set<Annotation> NO_ANNOTATIONS = Collections.emptySet();
public static final Type[] EMPTY_TYPE_ARRAY = new Type[] {};
Expand Down
39 changes: 7 additions & 32 deletions moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java
Expand Up @@ -15,23 +15,22 @@
*/
package com.squareup.moshi;

import static java.lang.invoke.MethodType.methodType;

import com.squareup.moshi.internal.Util;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.RecordComponent;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import static java.lang.invoke.MethodType.methodType;

/**
* A {@link JsonAdapter} that supports Java {@code record} classes via reflection.
*
Expand All @@ -54,39 +53,15 @@ final class RecordJsonAdapter<T> extends JsonAdapter<T> {
return null;
}

Map<String, Type> mappedTypeArgs = null;
if (type instanceof ParameterizedType parameterizedType) {
Type[] typeArgs = parameterizedType.getActualTypeArguments();
var typeVars = rawType.getTypeParameters();
mappedTypeArgs = new LinkedHashMap<>(typeArgs.length);
for (int i = 0; i < typeArgs.length; ++i) {
var typeVarName = typeVars[i].getName();
var materialized = typeArgs[i];
mappedTypeArgs.put(typeVarName, materialized);
}
}
var components = rawType.getRecordComponents();
var bindings = new LinkedHashMap<String, ComponentBinding<?>>();
var constructorParams = new Class<?>[components.length];
var componentRawTypes = new Class<?>[components.length];
var lookup = MethodHandles.lookup();
for (int i = 0, componentsLength = components.length; i < componentsLength; i++) {
RecordComponent component = components[i];
constructorParams[i] = component.getType();
componentRawTypes[i] = component.getType();
var name = component.getName();
var componentType = component.getGenericType();
if (componentType instanceof TypeVariable<?> typeVariable) {
var typeVarName = typeVariable.getName();
if (mappedTypeArgs == null) {
throw new AssertionError(
"No mapped type arguments found for type '" + typeVarName + "'");
}
var mappedType = mappedTypeArgs.get(typeVarName);
if (mappedType == null) {
throw new AssertionError(
"No materialized type argument found for type '" + typeVarName + "'");
}
componentType = mappedType;
}
var componentType = Util.resolve(type, rawType, component.getGenericType());
var jsonName = name;
Set<Annotation> qualifiers = null;
for (var annotation : component.getDeclaredAnnotations()) {
Expand Down Expand Up @@ -129,7 +104,7 @@ final class RecordJsonAdapter<T> extends JsonAdapter<T> {

MethodHandle constructor;
try {
constructor = lookup.findConstructor(rawType, methodType(void.class, constructorParams));
constructor = lookup.findConstructor(rawType, methodType(void.class, componentRawTypes));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError(e);
}
Expand Down

0 comments on commit e45655b

Please sign in to comment.