Giter Site home page Giter Site logo

zacsweers / moshix Goto Github PK

View Code? Open in Web Editor NEW
490.0 8.0 36.0 2.07 MB

Extensions for Moshi including IR plugins, moshi-sealed, and more.

License: Apache License 2.0

Kotlin 95.91% Shell 0.15% Java 3.94%
moshi kotlin json code-generation annotation-processor reflection kapt kotlin-reflect kotlinpoet sealed

moshix's Introduction

MoshiX

Extensions for Moshi

  • moshi-ir - A Kotlin IR implementation of Moshi and moshi-sealed code gen.
  • moshi-adapters - A collection of custom adapters for Moshi.
  • moshi-metadata-reflect - A kotlinx-metadata based implementation of KotlinJsonAdapterFactory. This allows for reflective Moshi serialization on Kotlin classes without the cost of including kotlin-reflect.
  • moshi-sealed - Reflective and code gen implementations for serializing Kotlin sealed classes via Moshi polymorphic adapters.

Snapshots of the development version are available in Sonatype's snapshots repository.

License

Copyright 2020 Zac Sweers

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

   https://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.

moshix's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

moshix's Issues

Visibility modifier other than public

I have a few data classes which have internal as their visibility modifier.

The code which generates the adapter creates public adapters which expose the types they reference and therefore result in a compilation failure.

If you're okay with it, I could make a PR to match the behavior of moshi-kotlin to use internal for the adapters as well if the classes are marked as such.

Abstract fun cannot contain code

Hi! I'm not entirely sure this is related to this project since it is an KotlinPoet error, however probably you can still help :D.

I have code like this:

@JsonAdapter(generateAdapter = true, generator = "sealed:type")
sealed class Block() {
	abstract fun doSomething(): AnotherClass?
}

@JsonAdapter(generateAdapter = true)
internal data class ImplementationBlock() {
	override fun mapToDomain = ImplementationBlockAnotherClass()
}

When I want to map this, I get a KotlinPoet error saying 'abstract fun cannot contain code'

So it is most likely related to the doSomething() function but I couldn't figure out more. I actually read in the KotlinPoet repo that this should be possible to do.

KSAnnotation.toAnnotationSpec() doesn't handle enum properties correctly

I have a class:

@JsonClass(generateAdapter = true)
data class BbaStatement(
    @[JsonLocalDate(zone = MST) Json(name = "startDateMst")] val startDate: Instant,
)

The annotation itself is pretty boring:

@[JsonQualifier Retention(AnnotationRetention.RUNTIME)]
annotation class JsonLocalDate(val zone: JsonTimeZone);

enum class JsonTimeZone { HST, AST, PST, MST, CST, EST }

The KSP-generated JsonAdapter has a field defined like so:

@field:JsonLocalDate(zone = JsonTimeZone.MST::class)
private val instantAtJsonLocalDateAdapter: JsonAdapter<Instant> =
  moshi.adapter(Instant::class.java, Types.getFieldJsonQualifierAnnotations(javaClass,
  "instantAtJsonLocalDateAdapter"), "startDate")

The annotation that tags the field is incorrect. When generated with KAPT, I see @field:JsonLocalDate(zone = JsonTimeZone.MST), without the ::class suffix on the enum.

Here's the runtime exception stacktrace I see that alerted me to the problem:

Caused by: java.lang.annotation.AnnotationTypeMismatchException: Incorrectly typed data found for annotation element public abstract hatch.common.annotations.JsonTimeZone hatch.common.annotations.JsonLocalDate.zone() (Found data of type java.lang.Class)
    at libcore.reflect.AnnotationMember.validateValue(AnnotationMember.java:351)
    at libcore.reflect.AnnotationFactory.invoke(AnnotationFactory.java:299)
    at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
    at $Proxy14.zone(Unknown Source)
	at com.squareup.moshi.Moshi.adapter(Moshi.java:145)hatch.core.network.adapters.LocalDateJsonAdapterFactory.create(LocalDateJsonAdapterFactory.kt:18)
	at hatch.model.dashboard.BbaStatementJsonAdapter.<init>(BbaStatementJsonAdapter.kt:32)

This seems to be an issue only for annotations with an enum value -- the correct adapter is generated if I use zone: String instead.

KSP codegen fails on data class w/ interface

given a class

interface SomeInterface{
	val a : String
}

data class SomeImpl(override val a : String, val b : String) : SomeInterface

I get the following error:

> Task :common:kaptGenerateStubsKotlin FAILED

...

e: [ksp] supertype [...].SomeInterface is not a Kotlin type

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':common:kaptGenerateStubsKotlin'.
> Compilation error. See log for more details

(package and class name modified to make this example easier to read)

As an aside, is that kapt task name a red herring?

moshi-sealed-reflect depends on SNAPSHOT version of moshi

When I first added moshi-sealed-reflect to my gradle file, I go an error complaining that gradle couldn't find com.squareup.moshi:moshi:1.9.0-SNAPSHOT

Poking around a bit, I noticed that https://github.com/ZacSweers/moshi-sealed/blob/master/moshi-sealed-reflect/build.gradle is referencing a snapshot version. Is that right?

FWIW, I managed to get past it by changing my app's gradle file to temporarily point moshi to the same SNAPSHOT version and then changed it back 1.9.2. For whatever reason, this is enough to stop gradle complaining.

Can't generate from supertype if supertype is in different gradle module

abstract class AbstractClassInModuleA
@JsonClass(generateAdapter = true)
data class DataClassInModuleB(
  val id : String
) : AbstractClassInModuleA

Error:

e: [ksp] @JsonClass can't be applied to DataClassInModuleB: supertype com.example.AbstractClassInModuleA is not a Kotlin type: DataClassInModuleB

Move DataClassInModuleB to module A all works as expected.

Again, probably a KSP bug where it's providing you the wrong type in the processor.

Fyi, module b includes module a via api(project(":modulea"))

Add type information to DefaultObject to improve debugging

@defaultobject annotation works fine to define unknown types and we log them to Cashlytics as non-fatal errors to track problems parsing items in production apps.

The problem is that these default objects do not include any information about the unknown type found in the original JSON response, it would be very helpful if we could add a string value to the default object/class to debug unknown types in the wild.

Example / Support for Registering Adapter Factory for External Libraries

We use Arrow.Core.Option pretty heavily which is essentially a sealed class:

sealed class Option<T> {
        data class Some<T> (val t: T) : Option<T>()
        object None : Option<Nothing>()
    }

Cannot add the @JsonClass(generateAdapter = true, generator = "sealed:type") to this class since it's defined in an external library. How does this library work in relation to external libraries?

Json annotation as typealias causes compilation error

Ran into this trying to switch from kapt(moshi.codegen) to ksp(moshix.ksp).

In our project, we have typealias Json = com.moshi.Json. Allows to type @Json("key") instead of @Json(name = "key") all over our codebase plus helps if we ever need to migrate to another json (de/)serialization library.

Added a reproducer in #112.

Allow `JsonString` to be used in Retrofit

This is for those cases where you want the json string of a response.

The 'usual' way would be to define your service method return type as ResponseBody and call ResponseBody.string() at the call site to get the json string but this slight indirection is not necessary

interface Service {

  @GET("the/good/stuff") suspend fun goodStuff(): ResposeBody
}
// myService.goodStuff().string()

Being able to just annotate the method with @JsonString with a return type of String is much better.

Right now the @JsonString annotation targets are PROPERTY, FIELD. Adding FUNCTION to that should fix this with no other changes.

Sealed class hierarchy error

Hey, I’m trying to have a sealed class inside another sealed class, like described bellow and it throws the following error:

error: Missing @TypeLabel public abstract class StrawBerry extends Fruit

Example:
@JsonClass(generateAdapter = true, generator = "sealed:type")
sealed class Fruit

sealed class Strawberry: Fruit()

@TypeLabel("a")
@JsonClass(generateAdapter = true)
data class SmallStrawberry: Strawberry()

@TypeLabel("b")
@JsonClass(generateAdapter = true)
data class BigStrawberry: Strawberry()

Am I doing something wrong or is this not supported at the moment?

Support for nested sealed classes?

Hi, I'm trying to migrate to moshi.sealed but this I'm not sure if is supported

@JsonClass(generateAdapter = true, generator = "sealed:type")
    sealed class Status {
        sealed class HasBonus : Status() {
            abstract val bonus: Double

            @TypeLabel("active")
            @JsonClass(generateAdapter = true)
            data class Active(override val bonus: Double) : HasBonus()

            @TypeLabel("eligible")
            @JsonClass(generateAdapter = true)
            data class Eligible(val id: String, override val bonus: Double) : HasBonus()

            @TypeLabel("missing_consumer")
            @JsonClass(generateAdapter = true)
            data class MissingConsumer(override val bonus: Double) : HasBonus()

            @TypeLabel("missing_contributor")
            @JsonClass(generateAdapter = true)
            data class MissingContributor(override val bonus: Double) : HasBonus()
        }

        @DefaultObject
        @TypeLabel("not_eligible")
        object NotEligible : Status()
    }
error: Missing @TypeLabel.
        public static abstract class HasBonus extends sk.o2.mojeo2.mcc.data.Mcc.Status {
                               ^

i.e. nested sealed classes

Support @TypeLabel on @DefaultObject to make it serializable

Hey! Thanks for moshi-sealed, we are using it heavily in our project.

With the support for object subtypes, I was wondering if it would be possible (and desireable) to support a type label on the default object. So basically making the default object a supported serializable and deserializable subtype.

I can see two use cases:

  1. It would allow serialization of the default object (see also #28). That's what I would be interested in. We often have a setup where we directly write a JSON API response to some cache file. If that response contains unknown subtypes that were mapped to the default object, this would fail since default objects are not serializable. The only workaround is to filter out the default object instances before writing. One downside though is that this can get more complex if there are sealed types in different levels of the object hierarchy. Another one is that we usually only want to do further processing and handling of unknown subtypes further down in the processing chain.
    Example:
@JsonClass(generateAdapter = true, generator = "sealed:type")
sealed class ListItem

@TypeLabel("typeA")
@JsonClass(generateAdapter = true)
data class ListItemA(...) : ListItem()

@TypeLabel("unknown")
@DefaultObject
object UnknownListItem : ListItem()

// --------
adapter.toJson(listOf(ListItemA, UnknownListItem))  // result: [{ "type": "typeA" }, { "type": "unknown" }]"
  1. It would allow to define one of the "supported" subtypes as a fallback for "unsupported" types. Not sure if there are many use-cases, but I could imagine something like a registered "Error" subtype where additionally all unregistered types would map to:
@JsonClass(generateAdapter = true, generator = "sealed:type")
sealed class OperationResult

@TypeLabel("success")
@JsonClass(generateAdapter = true)
data class Success(...) : OperationResult()

@TypeLabel("error")
@DefaultObject
object Error : OperationResult()

//----------------
adapter.fromJson(""" { "type": "error" } """)  // result: Error
adapter.fromJson(""" { "type": "some_unknown_result" } """)  // result: Error

The downside I see is that it would break symmetry of fromJSON/toJSON, but I guess it would be obvious enough from the semantics that nobody should be surprised about it.


My naive understanding is that something like this would be possible by generating an adapter that has the default value additionally registered as a valid subtype (refering to Example 1):

PolymorphicJsonAdapterFactory
        (...)
        .withSubtype(UnknownListItem::class.java, "unknown")
        .withDefaultValue(UnknownListItem)

Currently, adding a @TypeLabel to a @DefaultObject compiles fine, but the default object is not registered as a subtype.

Take class name per default

What would be nice for the codegen if we could make the TypeLabel annotation optional and per default just take the class name itself. So if I have for example

@JsonClass(generateAdapter = true, generator = "sealed:type")
sealed class Message()

@JsonClass(generateAdapter = true)
data class Unsecure(val msg: String): Message()

that it would expect the key type to be Unsecure in this case. That would reduce the boilerplate and also is what I would be happy with 99% of the time. What do you think?

Moshi-sealed-runtime: More than one file was found with OS independent path 'META-INF/runtime.kotlin_module'

After adding dev.zacsweers.moshix:moshi-sealed-runtime:0.11.2 dependency, project fails to build with the following error:
More than one file was found with OS independent path 'META-INF/runtime.kotlin_module'.
It seems like a module name mangling problem, as stated here https://ncorti.com/blog/name-mangling-in-kotlin
Possible solution is to pass the -module-name compiler flag in the library (in fact, it might prove useful to add this statement to all moshiX libraries)

Generics support in sealed

What would this look like? Is there anything we need to do? I'm not actually sure if PolymorphicJsonAdapter supports them either

sealed class Message<T> {
  data class Success<T>(val data: T)
  data class Failure(val reason: String)
}

Sealed interfaces

Right now this is tightly coupled to sealed classes, but is there space to generally support the other polymorphic type cases and make sealed classes just a specific subtype?

Kinds:

abstract/open class + subclasses

abstract class Message {
}

class Success : Message()

interface + implementations

interface Message {
}

class Success : Message

interface + subinterfaces

interface Message {
}

interface Success : Message

Java support

Right now this is oriented toward Kotlin sealed classes, but we could pretty easily extend it to support Java via #2. Would require a basic Java reflection artifact that does almost all of what the current reflect artifact does, then just make the kotlin-reflect artifact use this but provide a custom mapping of the sealed subtypes rather than the reflective implementation of reading annotations.

Compatibility with Anvil

Hi, so I'm trying moshi-ksp out, followed the instructions, got a minimal example working.

However in production app I use Anvil (https://github.com/square/anvil) user, so I applied to plugin and there seems to be some interference with ksp

> Task :app:kspDebugKotlin FAILED
e: Required plugin option not present: com.squareup.anvil.compiler:src-gen-dir

Plugin "com.squareup.anvil.compiler" usage:
  src-gen-dir <file-path>    Path to directory in which Anvil specific code should be generated (required)
  generate-dagger-factories <true|false>
                             Whether Anvil should generate Factory classes that the Dagger annotation processor would generate for @Provides methods and @Inject constructors.


e: Required plugin option not present: com.squareup.anvil.compiler:src-gen-dir

Any chance you know what is up or should I follow this up with Anvil guys

toJson on a @DefaultObject

Is it possible to do a toJson() on an object tree with DefaultObjects?

Running a toJson() on the result of a fromJson() doesn't work if any of the incoming types in the tree were unknown (@DefaultObject).

The error:

Expected one of [...] but found com.[].UnknownAction. Register this subtype. 
at com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory$PolymorphicJsonAdapter.toJson(PolymorphicJsonAdapterFactory.java:286) 

(UnknownAction is my @DefaultObject)

Is there a way around this?

DefaultObject and DefaultNull Unresolved reference withDefaultValue

When building using

apply plugin: 'com.android.application'
apply plugin: 'kotlin-platform-android'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.cren90.sandbox"
        minSdkVersion 23
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    implementation 'com.squareup.moshi:moshi-kotlin:1.9.2'
    implementation 'com.squareup.moshi:moshi-adapters:1.11.0'
    implementation 'dev.zacsweers.moshix:moshi-sealed-runtime:0.6.1'
    kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.9.2'
    kapt 'dev.zacsweers.moshix:moshi-sealed-codegen:0.6.1'
}

My sealed class and dependencies:

package com.cren90.sandbox.moshisealed

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import dev.zacsweers.moshix.sealed.annotations.TypeLabel
import dev.zacsweers.moshix.sealed.annotations.DefaultObject
import java.util.*

@JsonClass(generateAdapter = true,
           generator = "sealed:object_type")
//@DefaultNull //Not working
sealed class BaseType {
    @TypeLabel("type1")
    @JsonClass(generateAdapter = true)
    data class Type1(
        @Json(name = "credential") val credential: Type1Credential,
        @Json(name = "id") override val id: Int
        // more fields here
    ) : BaseType()

    @TypeLabel("type2")
    @JsonClass(generateAdapter = true)
    data class Type2(
        @Json(name = "credential") val credential: Type2Credential,
        @Json(name = "id") override val id: Int
        // more fields here
    ) : BaseType()

    @DefaultObject // Not working
    object UnknownType: BaseType() {
        override val id: Int = -1
        // more fields here
    }

    abstract val id: Int
    // more fields here
}

@JsonClass(generateAdapter = true)
data class Type1Credential(
    @Json(name = "device_public_key") val devicePublicKey: String,
    @Json(name = "device_serial") val deviceSerial: String,
    @Json(name = "device_uuid") val deviceUUID: UUID,
    @Json(name = "user_private_key") val userPrivateKey: String,
    @Json(name = "user_uuid") val userUUID: UUID
)
data class Type2Credential(
    @Json(name = "key") val key: String
)

And the generated adapter that is throwing the error

// Code generated by moshi-sealed. Do not edit.
package com.cren90.sandbox.moshisealed

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
import kotlin.Suppress
import kotlin.Unit
import kotlin.collections.emptySet

public class BaseTypeJsonAdapter(
  moshi: Moshi
) : JsonAdapter<BaseType>() {
  @Suppress("UNCHECKED_CAST")
  private val runtimeAdapter: JsonAdapter<BaseType> =
      PolymorphicJsonAdapterFactory.of(BaseType::class.java, "object_type")
        .withSubtype(BaseType.Type1::class.java, "type1")
        .withSubtype(BaseType.Type2::class.java, "type2")
        .withDefaultValue(BaseType.UnknownType) // When using @DefaultNull, BaseType.UnknownType is replaced with null here
        .create(BaseType::class.java, emptySet(), moshi) as JsonAdapter<BaseType>


  public override fun fromJson(reader: JsonReader): BaseType? = runtimeAdapter.fromJson(reader)

  public override fun toJson(writer: JsonWriter, value: BaseType?): Unit {
    runtimeAdapter.toJson(writer, value)
  }
}

I get the following build output

e: /Users/chris/Git/sandbox/appSandbox/build/generated/source/kapt/debug/com/cren90/sandbox/moshisealed/BaseTypeJsonAdapter.kt: (21, 10): Unresolved reference: withDefaultValue
e: /Users/chris/Git/sandbox/appSandbox/build/generated/source/kapt/debug/com/cren90/sandbox/moshisealed/BaseTypeJsonAdapter.kt: (22, 39): Type inference failed: Not enough information to infer parameter T in fun <T> emptySet(): Set<T>
Please specify it explicitly.

[KSP] No value found for value. Was null

Pretty sure this happens when there is a missing dependency on a module, not sure if it's possible yet, but would be good to surface the file/class that causes this?

e: java.lang.IllegalStateException: No value found for value. Was null
	at dev.zacsweers.moshix.ksp.MoshiApiUtilKt.generator(MoshiApiUtil.kt:138)
	at dev.zacsweers.moshix.ksp.JsonClassSymbolProcessor.adapterGenerator(JsonClassSymbolProcessor.kt:143)
	at dev.zacsweers.moshix.ksp.JsonClassSymbolProcessor.process(JsonClassSymbolProcessor.kt:95)

code-gen for map

I have a data classes as follows:

@JsonClass(generateAdapter = true)
data class Root(
    val id: String,
    val something: Something,
    val apps: Map<String, App>
)

@JsonClass(generateAdapter = true, generator = "sealed:apps")
sealed class App

@JsonClass(generateAdapter = true)
@TypeLabel("myApp")
data class MyApp(
    val b: Map<String, OtherDataClass>
) : App()
...

specifically where for val apps: Map<String, App> the key should be used as the label to choose in which of the data class the json can be converted to. Is that something that could be possible, without having to write my own adapter?

Unable to de/serialize sealed class of objects

I had this working previously with NavToken as an enum class. Trying to follow what's outlined in the readme, I have the following:

@JsonClass(generateAdapter = false, generator = "sealed:navToken")
sealed class NavToken() {
  @TypeLabel("day_screen")
  object MonthScreen : NavToken()

  @TypeLabel("month_screen")
  object DayScreen : NavToken()
}

@JsonClass(generateAdapter = true)
data class NavigationState(
  val backStack: List<NavToken> = listOf(NavToken.MonthScreen),
) 

@JsonClass(generateAdapter = true)
data class ApplicationState(
  val loading: Boolean = true,
  val calendarState: CalendarState = CalendarState(),
  val navigationState: NavigationState = NavigationState()
) {
    fun from(stateString: String): ApplicationState {
      return Moshi
          .Builder()
          .build()
          .adapter(ApplicationState::class.java)
          .fromJson(stateString)
        ?: throw IllegalStateException("Deserialization failed for state string")
    }
  }

Trying to deserialize application state using its from function gives me

java.lang.IllegalArgumentException: Cannot serialize abstract class com.carterhudson.kcalendar.navigation.NavToken
    for class com.carterhudson.kcalendar.navigation.NavToken

Dependencies:

    // Serialization - Moshi w/ Codegen
    implementation "com.squareup.moshi:moshi:1.11.0"
    kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.11.0'
    implementation 'com.squareup.moshi:moshi-adapters:1.11.0'
    // Moshi sealed class support
    implementation "dev.zacsweers.moshix:moshi-sealed-runtime:0.9.2"
    kapt "dev.zacsweers.moshix:moshi-sealed-codegen:0.9.2"

What am I missing? Was I naive to believe it would "just work" because of

No runtime Moshi instance configuration is needed, code gen will generate JsonAdapters in a way that Moshi understands natively.

Moshi-KSP generic typealias fails to compile

package com.example

import com.squareup.moshi.JsonClass

enum class Animal {
    Cat,
    Dog,
}

typealias AnimalMap<T> = Map<Animal, T>

@JsonClass(generateAdapter = true)
data class ApiResponse(
    val animalNames: AnimalMap<String>,
)

fails with the error

java.lang.IllegalStateException: No type argument found for T! Anaylzing co.mopo.android.data.http.ApiResponse
e: [ksp] java.lang.IllegalStateException: No type argument found for T! Anaylzing co.mopo.android.data.http.ApiResponse
	at dev.zacsweers.moshix.ksp.KotlinPoetSpKt$toTypeParameterResolver$typeParamResolver$1.invoke(KotlinPoetSp.kt:81)
	at dev.zacsweers.moshix.ksp.KotlinPoetSpKt$toTypeParameterResolver$typeParamResolver$1.invoke(KotlinPoetSp.kt:78)
	at dev.zacsweers.moshix.ksp.KotlinPoetSpKt$toTypeParameterResolver$resolver$1.get(KotlinPoetSp.kt:87)
	at dev.zacsweers.moshix.ksp.KotlinPoetSpKt.toTypeName(KotlinPoetSp.kt:51)
	at dev.zacsweers.moshix.ksp.KotlinPoetSpKt.toTypeName(KotlinPoetSp.kt:134)
	at dev.zacsweers.moshix.ksp.KotlinPoetSpKt.toTypeName(KotlinPoetSp.kt:50)
	at dev.zacsweers.moshix.ksp.KotlinPoetSpKt.toTypeName(KotlinPoetSp.kt:52)
	at dev.zacsweers.moshix.ksp.KotlinPoetSpKt.toTypeName(KotlinPoetSp.kt:145)
	at dev.zacsweers.moshix.ksp.TargetTypesKt.primaryConstructor(TargetTypes.kt:161)
	at dev.zacsweers.moshix.ksp.TargetTypesKt.targetType(TargetTypes.kt:80)
	at dev.zacsweers.moshix.ksp.JsonClassSymbolProcessor.adapterGenerator(JsonClassSymbolProcessorProvider.kt:167)
	at dev.zacsweers.moshix.ksp.JsonClassSymbolProcessor.process(JsonClassSymbolProcessorProvider.kt:119)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$4$1.invoke(KotlinSymbolProcessingExtension.kt:172)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$4$1.invoke(KotlinSymbolProcessingExtension.kt:171)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:249)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:171)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:119)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:85)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:514)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:505)
	at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:505)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:189)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:155)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:169)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:52)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:88)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1575)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)

Great work on this by the way, I've seen a nice improvement to my compile time!

Support KSP 1.4.20-dev-experimental-20201222

e: java.lang.NoSuchMethodError: com.google.devtools.ksp.processing.CodeGenerator$DefaultImpls.createNewFile$default(Lcom/google/devtools/ksp/processing/CodeGenerator;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/io/OutputStream;
	at dev.zacsweers.moshix.ksp.KotlinPoetSpKt.writeTo(KotlinPoetSp.kt:133)
	at dev.zacsweers.moshix.ksp.JsonClassSymbolProcessor.process(JsonClassSymbolProcessor.kt:105)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:112)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:116)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:93)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:557)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:83)
	at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:115)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:548)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:192)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:162)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:169)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:52)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:88)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1560)
	at sun.reflect.GeneratedMethodAccessor97.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:357)
	at sun.rmi.transport.Transport$1.run(Transport.java:200)
	at sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

Trying to get a minimal example going

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
    	...
        classpath "com.google.devtools.ksp:symbol-processing:1.4.20-dev-experimental-20201222"
    }
}
apply plugin: "kotlin"
apply plugin: "symbol-processing"

dependencies {
    implementation "com.squareup.moshi:moshi:1.11.0"
    ksp "dev.zacsweers.moshix:moshi-ksp:0.6.1"
}

Any clue what is this about? I'm on Windows btw

Custom Annotations require Retention value to be set

We use some custom annotations on our Json classes, KSP was failing until we provided a value for @Retention on those annotation definitions. (TBH, it's probably KSP not reading and providing the default retention value)

e.g:

@JsonQualifier
@Retention
@MustBeDocumented
annotation class LocalDateOnly

was changed to:

@JsonQualifier
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class LocalDateOnly

Stacktrace and example class for completeness

@Parcelize
@JsonClass(generateAdapter = true)
data class IdentificationParsingData(
    @Json(name = "id_number")
    val number: String?,
    @Json(name = "name")
    val fullName: String?,
    @Json(name = "date_of_birth")
    @LocalDateOnly val dob: Date?,
) : Parcelable
e: [ksp] java.lang.IllegalStateException: No value found for value. Was null
        at dev.zacsweers.moshix.ksp.MoshiApiUtilKt.generator(MoshiApiUtil.kt:155)
        at dev.zacsweers.moshix.ksp.JsonClassSymbolProcessor.adapterGenerator(JsonClassSymbolProcessorProvider.kt:171)
        at dev.zacsweers.moshix.ksp.JsonClassSymbolProcessor.process(JsonClassSymbolProcessorProvider.kt:119)
        at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$4$1.invoke(KotlinSymbolProcessingExtension.kt:172)
        at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$4$1.invoke(KotlinSymbolProcessingExtension.kt:171)

Alternative API designs

This is an issue to track alternative API designs for this library.

The current implementation is below. For the sake of argument, please keep alternative API proposals using the same Message example code.

@JsonClass(generateAdapter = true, generator = "sealed:type")
sealed class Message {

  @TypeLabel("success")
  @JsonClass(generateAdapter = true)
  data class Success(val value: String) : Message()

  @TypeLabel("error")
  @JsonClass(generateAdapter = true)
  data class Error(val error_logs: Map<String, Any>) : Message()

  @DefaultObject
  object Unknown : Message()
}

Allow a default class

It would be great if the default could be an class instead of an object or null. The use case around this is sometimes the schema evolves so that it adds a type, and it would be helpful to read old messages into a class.

Support nested sealed types in moshi-sealed

Right now moshi-sealed only supports one level of nesting. It would be nice if we could automatically recognize nested levels and defer to them.

One open question - do we want to try them or skip over them? The latter is easy but the former is difficult because I don't think PolymorphicJsonAdapterFactory allows that level of dynamism

Support Java sealed classes/interfaces

Java 15 supports sealed classes and interfaces in preview. Should MoshiX support it?

// @DefaultObject is not possible in java
@JsonClass(generateAdapter = true, generator = "sealed:type")
public sealed interface MessageInterface permits MessageInterface.Success, MessageInterface.Error {

  @TypeLabel(label = "success", alternateLabels = {"successful"})
  @JsonClass(generateAdapter = true)
  final record Success(String value) implements MessageInterface {
  }

  @TypeLabel(label = "error")
  @JsonClass(generateAdapter = true)
  final record Error(Map<String, Object> error_logs) implements MessageInterface {
  }
}
// @DefaultObject is not possible in java
@JsonClass(generateAdapter = true, generator = "sealed:type")
public sealed class MessageClass permits MessageClass.Success, MessageClass.Error {

  @TypeLabel(label = "success", alternateLabels = {"successful"})
  @JsonClass(generateAdapter = true)
  final class Success extends MessageClass {

    final String value;

    Success(String value) {
      this.value = value;
    }
  }

  @TypeLabel(label = "error")
  @JsonClass(generateAdapter = true)
  final class Error extends MessageClass {

    final Map<String, Object> error_logs;

    Error(Map<String, Object> error_logs) {
      this.error_logs = error_logs;
    }
  }
}

Question: objects support?

Is following class structure, with objects not carrying any additional information, somehow achievable using moshi-sealed?

@JsonClass(generateAdapter = true, generator = "sealed:type")
sealed class Message {

  @TypeLabel("success")
  @JsonClass(generateAdapter = true)
  data class Success(val value: String) : Message()

  @TypeLabel("error")
  @JsonClass(generateAdapter = true)
  object Error : Message()

  @TypeLabel("another_error")
  @JsonClass(generateAdapter = true)
  object AnotherError : Message()
}

This would allow us to differentiate between types using "when" clause:

when (message) {
  is Success -> ...
  Error -> ...
  AnotherError -> ...
}

However, this class structure causes annotation processing to fail:

error: @JsonClass can't be applied to com.myapp.Error: must be a Kotlin class

When I remove @JsonClass annotation, I get another error:

error: Unhandled object type, cannot serialize this.

As a workaround I (probably) can change Error/AnotherError to class, but this requires to suppress CanSealedSubClassBeObject warning raised by Kotlin compiler.

KSP generation fails for a class with no package

I was migrating a class from one module to another and I forgot to add a package line, so my class was something like:

//in Envelope.kt
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
class EnvelopedResponse<T>(
    val message: String? = null,
    val data: T? = null,
)

the :kspKotlin task failed with the following stacktrace:

e: [ksp] Error preparing EnvelopedResponse: com.squareup.kotlinpoet.UtilKt.failIfEscapeInvalid(Util.kt:261)
com.squareup.kotlinpoet.UtilKt.escapeIfNecessary(Util.kt:270)
com.squareup.kotlinpoet.UtilKt.escapeIfNecessary$default(Util.kt:267)
com.squareup.kotlinpoet.UtilKt$escapeSegmentsIfNecessary$2.invoke(Util.kt:293)
com.squareup.kotlinpoet.UtilKt$escapeSegmentsIfNecessary$2.invoke(Util.kt)
kotlin.text.StringsKt__AppendableKt.appendElement(Appendable.kt:85)
kotlin.collections.CollectionsKt___CollectionsKt.joinTo(_Collections.kt:3324)
kotlin.collections.CollectionsKt___CollectionsKt.joinToString(_Collections.kt:3341)
kotlin.collections.CollectionsKt___CollectionsKt.joinToString$default(_Collections.kt:3340)
com.squareup.kotlinpoet.UtilKt.escapeSegmentsIfNecessary(Util.kt:293)
com.squareup.kotlinpoet.UtilKt.escapeSegmentsIfNecessary$default(Util.kt:291)
com.squareup.kotlinpoet.ClassName.emit$kotlinpoet(TypeName.kt:449)
com.squareup.kotlinpoet.TypeName$cachedString$2.invoke(TypeName.kt:81)
com.squareup.kotlinpoet.TypeName$cachedString$2.invoke(TypeName.kt:70)
kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
com.squareup.kotlinpoet.TypeName.getCachedString(TypeName.kt)
com.squareup.kotlinpoet.TypeName.toString(TypeName.kt:110)
com.squareup.kotlinpoet.TypeName.hashCode(TypeName.kt:108)
dev.zacsweers.moshix.ksp.shade.api.DelegateKey.hashCode(DelegateKey.kt)
java.base/java.util.HashMap.hash(HashMap.java:340)
java.base/java.util.HashMap.put(HashMap.java:612)
java.base/java.util.HashSet.add(HashSet.java:221)
dev.zacsweers.moshix.ksp.shade.api.AdapterGenerator.generateType(AdapterGenerator.kt:795)
dev.zacsweers.moshix.ksp.shade.api.AdapterGenerator.prepare(AdapterGenerator.kt:178)
dev.zacsweers.moshix.ksp.JsonClassSymbolProcessor.process(JsonClassSymbolProcessor.kt:121)
com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$$inlined$forEach$lambda$2.invoke(KotlinSymbolProcessingExtension.kt:151)
com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$$inlined$forEach$lambda$2.invoke(KotlinSymbolProcessingExtension.kt:71)
com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:228)
com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:150)
org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:116)

Adding the package statement fixed it (and it should have been there anyway), but maybe it's an easy fix on your side as well?

moshi-ksp not working with Proguard/R8. Fails to find JsonAdapters at runtime

2021-05-31 15:41:32.070 27708-27708/sk.foo.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
    Process: sk.foo.myapplication, PID: 27708
    java.lang.RuntimeException: Unable to start activity ComponentInfo{sk.foo.myapplication/sk.foo.myapplication.MainActivity}: java.lang.RuntimeException: Failed to find the generated JsonAdapter class for class e.a.a.a
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3431)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7660)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.lang.RuntimeException: Failed to find the generated JsonAdapter class for class e.a.a.a
        at b.b.a.t.b.d(:579)
        at b.b.a.r$b.a(:61)
        at b.b.a.q.f(:145)
        at b.b.a.q.e(:105)
        at b.b.a.q.c(:79)
        at sk.foo.myapplication.MainActivity.onCreate(:15)
        at android.app.Activity.performCreate(Activity.java:8000)
        at android.app.Activity.performCreate(Activity.java:7984)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3404)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7660) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
     Caused by: java.lang.ClassNotFoundException: e.a.a.aJsonAdapter
        at java.lang.Class.classForName(Native Method)
        at java.lang.Class.forName(Class.java:454)
        at b.b.a.t.b.d(:553)
        at b.b.a.r$b.a(:61) 
        at b.b.a.q.f(:145) 
        at b.b.a.q.e(:105) 
        at b.b.a.q.c(:79) 
        at sk.foo.myapplication.MainActivity.onCreate(:15) 
        at android.app.Activity.performCreate(Activity.java:8000) 
        at android.app.Activity.performCreate(Activity.java:7984) 
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309) 
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3404) 
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7660) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
     Caused by: java.lang.ClassNotFoundException: Didn't find class "e.a.a.aJsonAdapter" on path: DexPathList[[zip file "/data/app/~~tnIvr9zy7BPu8D2CgmGVGw==/sk.foo.myapplication-FIQk2jedNbCD9gSoMV93RQ==/base.apk"],nativeLibraryDirectories=[/data/app/~~tnIvr9zy7BPu8D2CgmGVGw==/sk.foo.myapplication-FIQk2jedNbCD9gSoMV93RQ==/lib/arm64, /system/lib64, /system_ext/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:207)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at java.lang.Class.classForName(Native Method) 
        at java.lang.Class.forName(Class.java:454) 
        at b.b.a.t.b.d(:553) 
        at b.b.a.r$b.a(:61) 
        at b.b.a.q.f(:145) 
        at b.b.a.q.e(:105) 
        at b.b.a.q.c(:79) 
        at sk.foo.myapplication.MainActivity.onCreate(:15) 
        at android.app.Activity.performCreate(Activity.java:8000) 
        at android.app.Activity.performCreate(Activity.java:7984) 
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309) 
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3404) 
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7660) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 

Here is a repro project https://github.com/ursusursus/MoshiKspRepro

I setup minification in debug so you can see it right away.
I also put in a keep rule in proguard-rules.pro, you need both of them.

Note that even cleaning is busted, since if you uncomment the keep rules, it will work; and if you comment them out again, it will work. Only manually deleting /build folders AND uninstalling the app causes the crash to manifest again (as it should)

self referencing type parameters w/ more than 1 type parameter does not work

For this input, moshi ksp fails to compile (cannot find R while trying to resolve T).

@JsonClass(generateAdapter = true)
  open class Node<T : Node<T, R>, R : Node<R, T>> {
    var t : T? = null
    var r : R? = null
  }

  @JsonClass(generateAdapter = true)
  class StringNodeNumberNode(
  ) : Node<StringNodeNumberNode, NumberStringNode>() {
    var text: String = ""
  }

  @JsonClass(generateAdapter = true)
  class NumberStringNode(
  ) : Node<NumberStringNode, StringNodeNumberNode>() {
    var number: Int = 0
  }

I tried to patch thee compiler by registering type vars before resolving them, seems to fix it but it is not a clean implementation:
yigit@7bf262e

Failing test case before the fix:
https://github.com/yigit/MoshiX/blob/self-referencing-types/moshi-ksp/tests/src/test/kotlin/dev/zacsweers/moshix/ksp/DualKotlinTest.kt#L365

IR-based implementation

Moshi code gen supports standard annotation processing and KSP, but the real dream here would be to support generation directly via IR. There's likely a lot of prior art we could read from the Parcelize and Kotlinx Serialization implementations.

Int-based labels

Is it possible to use a non-string label? Currently working with a JSON like the following:

{
    "state": 0,
    "message": "..."
}

{
    "state": 1,
    "token": "..."
}

I've set up the sealed class with a DefaultObject and with the example I have, it always falls back to the default object instead of the concrete type. I've tested with a string value ("1") and it resolves to the correct type. However, the json structure is not something I can change.

@JsonClass(generateAdapter = true, generator = "sealed:state")
sealed class State {

    @TypeLabel("0")
    @JsonClass(generateAdapter = true)
    data class Loading(
        val message: String
    ) : State()

    @TypeLabel("1")
    @JsonClass(generateAdapter = true)
    data class Power(
        val token: String,
    ) : State()

    @DefaultObject
    object Unknown : State()
}

Is there an alternative to have an Int-based label?

Support multiple TypeLabels for a child

Unfortunately, there's a use-case on my project that needs a specific child of a sealed class to be converted supporting 2 different types.

I tried to repeat the @TypeLabel with different values, but this not supported yet. Would it be possible to support this use-case?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.