kotlinx.serialization icon indicating copy to clipboard operation
kotlinx.serialization copied to clipboard

Possibility of having an equivalent of @JsonProperty from Jackson (one-way @Transient)

Open yoobi opened this issue 5 years ago • 16 comments

Hello, I was searching in stackoverflow and around but didn't find any answer to my problem.

What is your use-case and why do you need this feature? Here is my class:

@Serializable
data class User(val username: String, val password: String)

I hash the password before inserting in the database and it works. For example in my database I have { "_id" : ObjectId("5e3b3966c486191615c766a3"), "username" : yoobi, "password" : "$2a$09$bU/smIMyNGcrRFcCtyshpO.Z5n1gR6he0NFJjwjULooKnZx1Hb8vG" However the object returned after the insert is my full User, with the password. I'd like to be able to serialize my password but not to deserialize it and even ignore it.

Describe the solution you'd like

Pretty much like Jackson do, an annotation to ignore either the getter or the setter.

If that already exists, my apologies I didn't find it.

EDIT to describe a bit more the solution proposed:

In Jackson there is an annotation @JsonProperty() that allows you to specified how you want to access the variable on which you put the annotation, there is Access.WRITE_ONLY, Access.READ_ONLY, Access.AUTO etc... That would be nice if we could have the same thing in kotlinx, when there is a WRITE_ONLY, the deserializer ignore the field just like with a @Transient

yoobi avatar Feb 05 '20 22:02 yoobi

Would something like this help? https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/custom_serializers.md#representing-classes-as-a-single-value You can call the hash function when serializing.

Also @Transient is the annotation I think you're talking about.

Dominaezzz avatar Feb 05 '20 22:02 Dominaezzz

I've tried to @Transient from kotlinx and @Serializer(with = Password::class)

For @Transient I get : `Invalid JSON at 184: Encountered an unknown key password``

I need to send the password hashed to my webservice so it can insert it into the database, but I don't want the serializer to ignore the passwordwhen deserializing.

I don't know if I'm clear enough. I'll give @Serializer another try.

yoobi avatar Feb 05 '20 22:02 yoobi

Am I right that you want @Transient, but only for deserialization? And property still should be written during serialization.

We do not have a built-in mechanism for that, so yes, custom serializer is a way to go for now.

sandwwraith avatar Feb 06 '20 11:02 sandwwraith

Am I right that you want @Transient, but only for deserialization? And property still should be written during serialization.

Yes exactly, I think that would be a neat feature to add in kotlinx. For now I can workaround with the custom serializer.

yoobi avatar Feb 06 '20 11:02 yoobi

When I try to compile my code I often get, this error is inconsistent sometimes it shows, sometimes it doesn't even though I haven't changed my code

java.lang.IllegalStateException: Backend Internal error: Exception during code generation
File being compiled at position: file:///----/PasswordParser.kt
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen.checkSerializability(SerializerCodegen.kt:37)
	at org.jetbrains.kotlin.codegen.CompilationErrorHandler.lambda$static$0(CompilationErrorHandler.java:35)
	at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generate(PackageCodegenImpl.java:76)
	at org.jetbrains.kotlin.codegen.DefaultCodegenFactory.generatePackage(CodegenFactory.kt:96)
	at org.jetbrains.kotlin.codegen.DefaultCodegenFactory.generateModule(CodegenFactory.kt:67)
	at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.doGenerateFiles(KotlinCodegenFacade.java:47)
	at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:39)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.generate(KotlinToJVMBytecodeCompiler.kt:637)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:195)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:165)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:55)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:84)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:42)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:104)
	at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:349)
	at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:105)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally(IncrementalCompilerRunner.kt:237)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:88)
	at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:606)
	at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:99)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1645)
	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)
Caused by: java.lang.IllegalStateException: Class Password have constructor parameters which are not properties and therefore it is not serializable automatically
	at org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen.checkSerializability(SerializerCodegen.kt:37)
	at org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen.generateSerializableClassPropertyIfNeeded(SerializerCodegen.kt:105)
	at org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen.generate(SerializerCodegen.kt:43)
	at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl$Companion.generateSerializerExtensions(SerializerCodegenImpl.kt:55)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationCodegenExtension.generateClassSyntheticParts(SerializationCodegenExtension.kt:30)
	at org.jetbrains.kotlin.codegen.ImplementationBodyCodegen.generateSyntheticPartsAfterBody(ImplementationBodyCodegen.java:438)
	at org.jetbrains.kotlin.codegen.MemberCodegen.generate(MemberCodegen.java:132)
	at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:302)
	at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:286)
	at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generateClassesAndObjectsInFile(PackageCodegenImpl.java:118)
	at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generateFile(PackageCodegenImpl.java:137)
	at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generate(PackageCodegenImpl.java:68)
	... 35 more

Here is my full serializer:

import kotlinx.serialization.*
import org.project.entity.Password


@Serializer(forClass = Password::class)
object PasswordParser : KSerializer<Password> {

    override fun serialize(encoder: Encoder, obj: Password) {
        encoder.encodeString(obj.pwd)
    } 

   override fun deserialize(decoder: Decoder): Password =
        Password("****")
}

My class:

@Serializable
data class User(val username: String, val password: Password)

@Serializable(with = PasswordParser::class)
data class Password(val pwd: String)

EDIT: I noticed that the real issue is java.lang.IllegalStateException: Class Password have constructor parameters which are not properties and therefore it is not serializable automatically So I changed my val pwd: String to var pwd: String then it does compile but I get the following error while deserializing Invalid JSON at 195: Expected string or non-null literal

yoobi avatar Feb 07 '20 09:02 yoobi

add decoder.decodeString() to your deserialize so parser would consume (unused) string

sandwwraith avatar Feb 10 '20 09:02 sandwwraith

After a closer look, the custom serializer doesn't really answer my question, so i'll update it in this post. For example I'm sending this JSON to my service:

{
    "username": "yoobi",
    "password":"thisIsAStrongPassword"
}

with the PasswordParser the response is like this:

{
    "username":"yoobi",
    "password":"***"
}

and I'd like the response to be :

{
    "username":"yoobi"
}

Is it possible to achieve this ?

yoobi avatar Feb 11 '20 13:02 yoobi

little up

yoobi avatar Feb 19 '20 13:02 yoobi

write a custom serializer for User which would either ignore or replace password

sandwwraith avatar Feb 19 '20 15:02 sandwwraith

I already did write a custom serializer but it's not what I need, as stated earlier:

with the PasswordParser the response is like this:

{
    "username":"yoobi",
    "password":"***"
}

and I'd like the response to be :

{
    "username":"yoobi"
}

If I don't specify any fun deserialize() in my parser the return is still:

{
    "username":"yoobi",
    "password":"{}"
}

yoobi avatar Feb 19 '20 15:02 yoobi

I'm running in the same usecase as Yoobi. (Using his class as example) I can make a part of the associate deserializer but I don't know what to do into deserialize function.

How do you achieve this ?

object UserParser : KSerializer {

    override fun serialize(encoder: Encoder, obj: User) {
        //Don't encode password
        encoder.encodeString(obj.username)
    }
    override fun deserialize(decoder: Decoder): User {
        //Don't know how to decode
    }
}

Bastosss77 avatar Feb 19 '20 17:02 Bastosss77

write a custom serializer for User which would either ignore or replace password

What I tried:

  1. no body in override fun serialize, I got this error:
[ktor-jetty-8080-1] ERROR Application - Unhandled: PATCH - users/5e58340d123fd6411ea4e552
org.bson.json.JsonParseException: JSON reader was expecting a value but found '}' at org.bson.json.JsonReader.readBsonType(JsonReader.java:270)
  1. No override fun serialize, my json looks like this {"password":{}}

Conclusion: To come back to my first post, Custom serializer cannot be used in such usecase, my request for a new feature is (I think) legitimate as currently we cannot set a variable Read-Only or a Write-Only. To solve this issue kotlinx could add an annotation on the variable to be able to specify it is either a Read-Only or a Write-Only

yoobi avatar Feb 27 '20 22:02 yoobi

I have the same use case, Jackson supports the @JsonProperty, maybe a solution would be to allow @Transient on getter/setter only?

Caryntjen avatar Nov 01 '21 13:11 Caryntjen

Is there any news on "one-way @Transient" feature?

ArtemMikhaylov avatar Jan 03 '23 15:01 ArtemMikhaylov

hello, any updates on this?

m4rcingsxr avatar Nov 06 '25 14:11 m4rcingsxr

No

sandwwraith avatar Nov 06 '25 14:11 sandwwraith