parceler icon indicating copy to clipboard operation
parceler copied to clipboard

Parceler: Types do not match for property java.util.List<? extends

Open johnjohndoe opened this issue 6 years ago • 6 comments

I am trying to convert the following working JUnit test from Java ...

import android.os.Parcelable;
import org.junit.Test;
import org.parceler.Parcel;
import org.parceler.ParcelPropertyConverter;
import org.parceler.Parcels;
import java.util.List;
import com.example.utils.LowEmissionZoneBuilder;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;

public class ChildZonesParcelConverterTest {

    @Test
    public void testChildZonesParcelConverter() {
        LowEmissionZone lowEmissionZone = new LowEmissionZoneBuilder().setName("LowEmissionZone1").build();
        ParentZone parentZone = new ParentZone("ParentZone1", singletonList(lowEmissionZone));
        Parcelable parcelable = Parcels.wrap(parentZone);
        assertThat((ParentZone) Parcels.unwrap(parcelable)).isEqualTo(parentZone);
    }

    @Parcel
    static class ParentZone {

        public String name;

        @ParcelPropertyConverter(ChildZonesParcelConverter.class)
        public List<ChildZone> childZones;

        public ParentZone() {
            // For Parceler
        }

        public ParentZone(String name, List<ChildZone> childZones) {
            this.name = name;
            this.childZones = childZones;
        }

    }
}

... to Kotlin.

import com.example.utils.LowEmissionZoneBuilder
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.parceler.Parcel
import org.parceler.ParcelConstructor
import org.parceler.ParcelPropertyConverter
import org.parceler.Parcels

class ChildZonesParcelConverterTest {

    @Test
    fun testChildZonesParcelConverter() {
        val lowEmissionZone = LowEmissionZoneBuilder().setName("LowEmissionZone1").build()
        val parentZone = ParentZone("ParentZone1", listOf<ChildZone>(lowEmissionZone))
        val parcelable = Parcels.wrap(parentZone)
        assertThat(Parcels.unwrap<ParentZone>(parcelable)).isEqualTo(parentZone)
    }

    @Parcel(Parcel.Serialization.BEAN)
    internal data class ParentZone @ParcelConstructor constructor(

            val name: String,

            @ParcelPropertyConverter(ChildZonesParcelConverter::class)
            val childZones: List<ChildZone>

    )
}

The relationship between ChildZone and LowEmissionZone is as follows:

@Parcel
public class LowEmissionZone implements ChildZone {}

Executing the Kotlin test fails with the following error:

e: /build/tmp/kapt3/stubs/debugUnitTest/com.example/models/ChildZonesParcelConverterTest.java:38: error: 
    Parceler: Types do not match for property childZones 
    java.util.List<? extends com.example.models.ChildZone> 
    java.util.List<com.example.models.ChildZone>
        java.util.List<? extends com.example.models.ChildZone> childZones) {
                                                                        ^
e: /build/tmp/kapt3/stubs/debugUnitTest/com.example/models/ChildZonesParcelConverterTest.java:30: error: 
    Parceler: Types do not match for property childZones 
    java.util.List<? extends com.example.models.ChildZone> 
    java.util.List<com.example.models.ChildZone>
        public final java.util.List<com.example.models.ChildZone> getChildZones() {

Related

johnjohndoe avatar Feb 08 '19 16:02 johnjohndoe

Hmmm, are you able to show the Java classes Kotlin is generating for ParentZone @johnjohndoe? Looks like the Java constructor is accepting List<? extends ChildZone> while the getter is providing List<ChildZone>... which is appropriate. We might be able to accommodate this by relaxing some of the matching constraints in Parceler between the mutators and accessors.

johncarl81 avatar Feb 08 '19 17:02 johncarl81

Here is what I can find in build/tmp/kapt3/stubs/debugUnitTest/com/example/models/ChildZonesParcelConverterTest.java:

import java.lang.System;

@kotlin.Metadata(...)
public final class ChildZonesParcelConverterTest {
    
    @org.junit.Test()
    public final void testChildZonesParcelConverter() {
    }
    
    public ChildZonesParcelConverterTest() {
        super();
    }
    
    @kotlin.Metadata(...)
    @org.parceler.Parcel(value = org.parceler.Parcel.Serialization.BEAN)
    public static final class ParentZone {

        @org.jetbrains.annotations.NotNull()
        private final java.lang.String name = null;

        @org.jetbrains.annotations.NotNull()
        private java.util.List<? extends com.example.models.ChildZone> childZones;
        
        @org.jetbrains.annotations.NotNull()
        public final java.lang.String getName() {
            return null;
        }
        
        @org.jetbrains.annotations.NotNull()
        public final java.util.List<com.example.models.ChildZone> getChildZones() {
            return null;
        }
        
        public final void setChildZones(@org.jetbrains.annotations.NotNull()
        java.util.List<? extends com.example.models.ChildZone> p0) {
        }
        
        @org.parceler.ParcelConstructor()
        public ParentZone(@org.jetbrains.annotations.NotNull() java.lang.String name, 
        @org.jetbrains.annotations.NotNull()
        @org.parceler.ParcelPropertyConverter(value = com.example.models.ChildZonesParcelConverter.class) 
        java.util.List<? extends com.example.models.ChildZone> childZones) {
            super();
        }
        
        @org.jetbrains.annotations.NotNull()
        public final java.lang.String component1() {
            return null;
        }
        
        @org.jetbrains.annotations.NotNull()
        public final java.util.List<com.example.models.ChildZone> component2() {
            return null;
        }
        
        @org.jetbrains.annotations.NotNull()
        public final com.example.models.ChildZonesParcelConverterTest.ParentZone copy(
        @org.jetbrains.annotations.NotNull()
        java.lang.String name, 
        @org.jetbrains.annotations.NotNull()
        @org.parceler.ParcelPropertyConverter(value = com.example.models.ChildZonesParcelConverter.class)
        java.util.List<? extends com.example.models.ChildZone> childZones) {
            return null;
        }
        
        @org.jetbrains.annotations.NotNull()
        @java.lang.Override()
        public java.lang.String toString() {
            return null;
        }
        
        @java.lang.Override()
        public int hashCode() {
            return 0;
        }
        
        @java.lang.Override()
        public boolean equals(@org.jetbrains.annotations.Nullable()
        java.lang.Object p0) {
            return false;
        }
    }
}

johnjohndoe avatar Feb 08 '19 18:02 johnjohndoe

Ah, that's perfect @johnjohndoe, thanks! I need to think on this a bit...

johncarl81 avatar Feb 08 '19 20:02 johncarl81

Here's a failing unit test to work off of, if you want to get your hands dirty @johnjohndoe:

Add this to ParcelableAnalysisTest:

    @Parcel(Serialization.BEAN)
    static class GenericMixedExtends {
        private List<String> values;

        public List<String> getValues() {
            return values;
        }

        public void setValues(List<? extends String> values) {
            this.values = new ArrayList<String>();
            this.values.addAll(values);
        }
    }

    @Test
    public void testMixedGenericExtends() {
        ParcelableDescriptor analysis = analyze(GenericMixedExtends.class);

        assertFalse(messager.getMessage(), messager.isErrored()); // Fails here
        assertNull(analysis.getParcelConverterType());
        assertNotNull(analysis.getConstructorPair());
        assertEquals(0, analysis.getFieldPairs().size());
        assertEquals(1, analysis.getMethodPairs().size());
        assertEquals(0, analysis.getConstructorPair().getWriteReferences().size());
        assertEquals(0, analysis.getWrapCallbacks().size());
        assertEquals(0, analysis.getUnwrapCallbacks().size());
    }

changingList<? extends String> to just List<String> succeeds.

The indicated line fails with: Parceler: Types do not match for property values java.util.List<? extends java.lang.String> java.util.List<java.lang.String>. Basically if we could do a better job here, perhaps not just doing an equality, but a mutator matches accessor sort of thing that respects generics better then this wouldn't be an issue anymore.

johncarl81 avatar Feb 11 '19 20:02 johncarl81

Thank you for pointing into the source code!

However, I stepped away from using Kotlin data classes at the moment. The reason is that this would require me to add the jackson-module-kotlin library to the dependencies of my project which itself depends on kotlin-reflect. The latter is known for increasing the size of the final APK. I am trying to avoid this.

Therefore, I will not work on the topic in the near future.

johnjohndoe avatar Feb 22 '19 09:02 johnjohndoe

Fair enough. I do appreciate this issue @johnjohndoe. Let me know if you need anything else.

johncarl81 avatar Feb 22 '19 17:02 johncarl81