After configuring Kotlin for Android project, I wrote a simple MainActivity.kt
. It called Retrofit to get a JSON file which contained the following data:
{ "message": "success", "user": { "username": "Eric" } }
Now I want to use Moshi to convert the JSON data to Kotlin's class, so here are the two classes to reflect the above JSON structure:
class User(var username: String) class UserJson(var message: String, var user: User)
And a custom type adapter for Moshi:
class UserAdapter { @FromJson fun fromJson(userJson: UserJson) : User { Log.d("MyLog", "message = ${userJson.message}") // = success Log.d("MyLog", "user = ${userJson.user}") // = null return userJson.user } }
When it goes into the function fromJson()
, userJson.message = "success"
as expected. But the strange thing is that userJson.user
is null
, which should be User(username="Eric")
.
I am new to Moshi and Kotlin, and I have already stuck with this problem for about 10 hours. Please help me out. Thanks for any help.
========================================
The following is the entire code of MainActivity.kt
(50 lines only):
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Custom Type Adapters for Moshi val userMoshi = Moshi.Builder().add(UserAdapter()).build() val retrofit = Retrofit.Builder() .baseUrl("https://dl.dropboxusercontent.com/") .addConverterFactory(MoshiConverterFactory.create(userMoshi)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build() val accountService = retrofit.create(AccountService::class.java) accountService.signUpAnonymously() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { user -> Log.d("MyLog", user.toString()) } } } // ========== For Retrofit ========== interface AccountService { @GET("u/17350105/test.json") fun signUpAnonymously() : Observable<User> } // ========== For Moshi ========== class User(var username: String) class UserJson(var message: String, var user: User) class UserAdapter { @FromJson fun fromJson(userJson: UserJson) : User { Log.d("MyLog", "message = ${userJson.message}") // = success Log.d("MyLog", "user = ${userJson.user}") // = null return userJson.user } }
The build.gradle
is:
compile "io.reactivex.rxjava2:rxjava:2.0.0" compile "io.reactivex.rxjava2:rxandroid:2.0.0" compile "com.android.support:appcompat-v7:25.0.0" compile "com.squareup.retrofit2:retrofit:2.1.0" compile "com.squareup.retrofit2:converter-moshi:2.1.0" compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
Thank you again.
You can solve the problem by changing your code to do something like below.
Basically in your case when the UserAdapter
is registered, it tells moshi that it can create a User
only from UserJson
object. Hence Moshi does not recognize the JSON object with keyword user
.
By adding an indirection in form of User1
(please pardon the naming convention), the UserJson
is created properly with User1
from JSON.
class User(var username: String)
class User1(var username: String) // I introduced this class
class UserJson(var message: String, var user: User1) // changed User to User1
class UserAdapter {
@FromJson fun fromJson(userJson: UserJson): User {
println("message = ${userJson.message}")
println("user = ${userJson.user}")
return User(userJson.user.username)
}
}
If you just need the User
object. There is a library called Moshi-Lazy-Adapters that provides a @Wrapped
annotation, that allows specifying the path to the desired object. All you have to do is add the respective adapter to your Moshi instance and change the service code to:
interface AccountService {
@GET("u/17350105/test.json")
@Wrapped("user")
fun signUpAnonymously() : Observable<User>
}
No need for any other custom adapter.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With