Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find a way to not depend on runtime reflection #1

Open
deusaquilus opened this issue Dec 30, 2023 · 10 comments
Open

Find a way to not depend on runtime reflection #1

deusaquilus opened this issue Dec 30, 2023 · 10 comments

Comments

@deusaquilus
Copy link
Owner

deusaquilus commented Dec 30, 2023

This library uses kotlin-reflect in the Walker in order to recursively get properties from each class as well as the class names. This functionality can be replaced with kotlinx-serialization functionality fairly easily. I have a branch that does exactly this. Can develop this further if there is an ask.
https://github.com/deusaquilus/pprint-kotlin/tree/using-kotlinx-serialization

Screenshot from 2023-12-31 03-13-49

@sureshg
Copy link

sureshg commented Jan 3, 2024

I would really like to have a multiplatform solution without using kotlin-reflect. So does this approach require the classes to be annotated with @Serializable ? Can we use kotlin-metdata-jvm that will be lighter than kotlin-reflect ? Not sure if we can use this for multiplatform support though

@deusaquilus
Copy link
Owner Author

Yeah, this solution would require @Serializeable. Is that a problem?

@sureshg
Copy link

sureshg commented Jan 3, 2024

Most of the time you are not in charge of the data class component to annotate... what if data classes contains other classes from third party deps?

@saket
Copy link

saket commented Jan 6, 2024

I see logging as a hassle-free way to print debug information. Being forced to annotate my data classes with @Serializable before I'm able to print them would be very unfortunate, especially for classes that I do not own as @sureshg already mentioned.

@deusaquilus
Copy link
Owner Author

deusaquilus commented Jan 7, 2024

Turns out kotlinx-serialization has a way to do that. If it's a simple Kotlin class with a properties-only constructor (i.e. all data classes are this way as far as I know) you can just do:

// NOT @Serializable
class ClassThatIDontControl(val foo: String, val bar: Int)
                           
@Serializer(forClass = ClassIDontControl::class)
object ClassIDontControlSerializer

// Then the pprint API would look something like this 
val instance = ClassIDontControl("hello", 123)
println(pprint(ClassIDontControlSerializer, instance))

Have a look here for info.

Now if the ClassThatIDontControl is more complex e.g. a Java class you'll have to write the ClassThatIDontControlSerializer in a more manual way. Have a look at Delegating Serializers for more info on that. I imagine it would be something like:

// Imagine this is coming from some 3rd-party dependency
public class ClassIDontControl {
  private String foo;
  private Int bar;
  public String getFoo { return foo; }
  public String getBar { return bar; }
  // blah blah blah constructors, initializers, etc...
}
// Then in your application you'd do...
class ClassIDoControl(foo: String, bar: Int)

class ClassIDontControlSerializer : KSerializer<ClassIDontControl> {
    private val delegateSerializer = ClassIDoControl.serializer()
    override val descriptor = SerialDescriptor("ClassIDontControl", delegateSerializer.descriptor)

    override fun serialize(encoder: Encoder, value: ClassIDontControl) {
        val data = ClassIDoControl(value.getFoo(), value.getBar())
        encoder.encodeSerializableValue(delegateSerializer, data)
    }

    override fun deserialize(decoder: Decoder): ClassIDontControl {
        val data: ClassIDoControl = decoder.decodeSerializableValue(delegateSerializer)
        return ClassIDontControl.constructWhateverWayYouNeed(data.foo, data.bar)
    }
}
                           
@Serializer(forClass = ClassThatIDontControl::class)
object ClassIDontControlSerializer

// Then the pprint API would look something like this 
val instance: ClassIDontControl = getItFromWhereverYouNeed(...)
println(pprint(ClassIDontControlSerializer, instance))

You can learn more about delegating serializers here.

Bottom line, it's doable. The mighty kotlinx-serialization people have thought about these use-cases.

@saket
Copy link

saket commented Jan 7, 2024

Bottom line, it's doable. The mighty kotlinx-serialization people have thought about these use-cases.

I'm sorry I should have clarified this -- we're aware that it is doable, but the effort needed to make them serializable can be non-trivial which will discourage us from using pprint.

I'm a mobile developer so my experience may be different from other platforms, but I've never seen anyone check-in println statements to production code. It is always used for temporary debugging sessions as an inexpensive way to aid/avoid the IDE debugger. I'd love to use pprint for printing nicer, readable logs but I'd not use it if it required my classes to be serializable.

Plus what happens to projects that use libraries other than kotlinx.serializable such as moshi?

@deusaquilus
Copy link
Owner Author

I’m confused. Do you want to use kotlin-reflect, moshi, or something else?

@saket
Copy link

saket commented Jan 8, 2024

I'm suggesting to continue using reflection.

@deusaquilus
Copy link
Owner Author

I won't remove any of the current reflection-based functionality. I'll add a new module (probably pprint-multiplatform) that does the non-reflection based stuff.

@saket
Copy link

saket commented Jan 9, 2024

Sounds superb!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants