square / moshi

A modern JSON library for Kotlin and Java.
https://square.github.io/moshi/1.x/
Apache License 2.0
9.73k stars 758 forks source link

Conflicts using `transient` (it is also used by other libraries) #1262

Closed goa closed 3 years ago

goa commented 3 years ago

As mentioned in the last comments of this issue, other libraries such as Realm and Cupboard are also using transient to exclude fields.

What if we need a field to be excluded from JSON serialization, but nevertheless be written to the Realm database? If transient is used, the field will be excluded from both.

I wonder if Moshi could use another dedicated annotation to exclude fields, or at least an annotation to force inclusion of transient fields.

JakeWharton commented 3 years ago

It's not a good idea to mix model objects across serialization formats. You either should persist the raw JSON or split the models for the networking layer and the persistence layer. Changes in schema shouldn't affect JSON serialization. Changes in JSON format shouldn't affect schema.

goa commented 3 years ago

While I agree that that mixing model objects is not the best approach, it's usually much less work when developing smaller to medium apps. Furthermore, this approach is possible for example with GSON and Realm, since GSON has its own approach for including / excluding fields, but it is impossible with Moshi and Realm since both libraries rely on transient.

While I understand that transient is The Right Way, I believe that adding an alternative way to exclude (or force-include transient) fields would be helpful in many situations to save code and increase adoption of Moshi in older projects.

I guess that if there are no other requests for this, the issue could be closed.

davo417 commented 3 years ago

I'm really holded back in GSON becouse of this missing feature on Moshi, and I don't want to be like this :'(

Also I would like to have more control over serialization and deserialization differences at runtime, for example, I work with a restfull API that offer data with ID fields, I want to deserialize this field for sync propouses but I don't want to serialize an ID when I post a new object to the API becouse it'll be useless, the API will override the value for obvious reasons.

According to the documentation:

Transient fields are omitted when writing JSON. When reading JSON, the field is skipped even if the JSON contains a value for the field. Instead it will get a default value.

So my @Exclude(serialize=false, deserialize=true) can't be safely migrated to Moshi.

ZacSweers commented 3 years ago

You should solve this case in your code base by implementing custom Moshi json adapters for types that need special behavior for different properties.

davo417 commented 3 years ago

You should solve this case in your code base by implementing custom Moshi json adapters for types that need special behavior for different properties.

Yes but I'll have to write the serializer by myself in all my datatypes so why do I use Moshi anyway, the idea is to save all the work and with GSON I can saveit

davo417 commented 3 years ago

You should solve this case in your code base by implementing custom Moshi json adapters for types that need special behavior for different properties.

I just did this:

@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FUNCTION)
internal annotation class ID

class IDAdapter {
    @ToJson
    fun toJson(@ID id: Int): String? = null

    @FromJson
    @ID
    fun fromJson(id: String): Int {
        return id.toInt()
    }
}

@JsonClass(generateAdapter = true)
data class TestID(
    @ID var id: Int,
    @Json(name = "name") var nameFiled: String
)

And got my expected result

fun main() {
    val dataIn = TestID(
        id = 1,
        nameFiled = "David"
    )

    val dataOut = "{\"id\":2, \"name\": \"David\"}"

    val moshi = Moshi.Builder()
        .add(IDAdapter())
        .build()

    val adapter = moshi.adapter(TestID::class.java)

    val outString = adapter.toJson(dataIn)
    println(outString)

    val inClass = adapter.fromJson(dataOut)
    println(inClass)
}

Output:

{"name":"David"}
TestID(id=2, nameFiled=David)

That would do for now... an annotation preprocessor like @Expose(serialize=false, deserialize=true) instead of a fixed @ID for a more flexible field adapter is better, of couse, I'll try to write it down...