Possibility of having an equivalent of @JsonProperty from Jackson (one-way @Transient)
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
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.
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.
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.
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.
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
add decoder.decodeString() to your deserialize so parser would consume (unused) string
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 ?
little up
write a custom serializer for User which would either ignore or replace password
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":"{}"
}
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
}
}
write a custom serializer for
Userwhich would either ignore or replace password
What I tried:
- 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)
- 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
I have the same use case, Jackson supports the @JsonProperty, maybe a solution would be to allow @Transient on getter/setter only?
Is there any news on "one-way @Transient" feature?
hello, any updates on this?
No