Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

moshi custom qualifier annotation to serialise null on one property only

I'd like to serialise null for only one property in my JSON body that is going on a PUT. I don't want to serialize null for any other types in the object. Model class is like this

@Parcel
class User @ParcelConstructor constructor(var college: College?,
                                          var firstname: String?,
                                          var lastname: String?,
                                          var email: String?,
                                          var active: Boolean = true,
                                          var updatedAt: String?,
                                          var gender: String?,
                                          var picture: String?,
                                          var id: String?,
                                          @field: [CollegeField] var collegeInput: String?,
                                          @field: [CollegeField] var otherCollege: String?,)

I only want to serialise collegeInput and otherCollege fields if either of them are null. For example

val user = User(firstname = "foo", lastname=null, collegeInput="abcd", otherCollege = null)

Json will look something like this:

{"user":{
  "firstname": "foo",
  "collegeInput": "abcd",
  "otherCollege": null
}}

Where otherCollege is null, lastname is omitted from the object as by default moshi does not serialise nulls which is what I want, but qualifer fields should be serialized with null values

I tried using

class UserAdapter {
@FromJson
@CollegeField
@Throws(Exception::class)
fun fromJson(reader: JsonReader): String? {
    return when (reader.peek()) {
        JsonReader.Token.NULL ->
            reader.nextNull()
        JsonReader.Token.STRING -> reader.nextString()
        else -> {
            reader.skipValue() // or throw
            null
        }
    }
}

@ToJson
@Throws(IOException::class)
fun toJson(@CollegeField b: String?): String? {
    return b
}


@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class CollegeField

I added the adapter to moshi but it never gets called

@Provides
@Singleton
fun provideMoshi(): Moshi {
    return Moshi.Builder()
            .add(UserAdapter())
            .build()
}

@Provides
@Singleton
fun provideRetrofit(client: OkHttpClient, moshi: Moshi, apiConfig: ApiConfig): Retrofit {
    return Retrofit.Builder()
            .baseUrl(apiConfig.baseUrl)
            .client(client)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(ScalarsConverterFactory.create())
            .addConverterFactory(MoshiConverterFactory.create(moshi))
            .build()
}
like image 835
Jaskaran Singh Avatar asked Sep 10 '18 09:09

Jaskaran Singh


People also ask

Why can’t Moshi assign the field’s default value?

If the class doesn’t have a no-arguments constructor, Moshi can’t assign the field’s default value, even if it’s specified in the field declaration. Instead, the field’s default is always 0 for numbers, false for booleans, and null for references.

How do I connect Moshi to a data class?

To hook it all up and parse the json to the data class you need to create a Moshi object, create the adapter instance and then pass the JSON to the adapter: If the JSON response changes and sets a null field in the JSON then the adapter will fail respecting the non null reference of a val property in the data class and throw a clear exception.

What is jsonqualifier in Moshi?

} Because JSON field names are always defined with their Java or Kotlin fields, Moshi makes it easy to find fields when navigating between Java or Koltin and JSON. Use @JsonQualifier to customize how a type is encoded for some fields without changing its encoding everywhere.

How do I change the name of a field in Moshi?

We can use the @Json annotation to give a new name to any field in any bean that we control: Once we've done this, Moshi immediately understands that this field has a different name in the JSON: 6.2. Transient Fields In certain cases, we may have fields that should not be included in the JSON.


1 Answers

Your toJson adapter method will return null when the qualified string value is null, and the JsonWriter will not write the null value.

Here is a qualifier and adapter factory to install that will work.

@Retention(RUNTIME)
@JsonQualifier
public @interface SerializeNulls {
  JsonAdapter.Factory JSON_ADAPTER_FACTORY = new JsonAdapter.Factory() {
    @Nullable @Override
    public JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
      Set<? extends Annotation> nextAnnotations =
          Types.nextAnnotations(annotations, SerializeNulls.class);
      if (nextAnnotations == null) {
        return null;
      }
      return moshi.nextAdapter(this, type, nextAnnotations).serializeNulls();
    }
  };
}

Now, the following will pass.

class User(
  var firstname: String?,
  var lastname: String?,
  @SerializeNulls var collegeInput: String?,
  @SerializeNulls var otherCollege: String?
)

@Test fun serializeNullsQualifier() {
  val moshi = Moshi.Builder()
      .add(SerializeNulls.JSON_ADAPTER_FACTORY)
      .add(KotlinJsonAdapterFactory())
      .build()
  val userAdapter = moshi.adapter(User::class.java)
  val user = User(
      firstname = "foo",
      lastname = null,
      collegeInput = "abcd",
      otherCollege = null
  )
  assertThat(
      userAdapter.toJson(user)
  ).isEqualTo(
      """{"firstname":"foo","collegeInput":"abcd","otherCollege":null}"""
  )
}

Note that you should use the Kotlin support in Moshi to avoid the @field: oddities.

like image 144
Eric Cochran Avatar answered Sep 27 '22 21:09

Eric Cochran