Closed Chuckame closed 7 months ago
Do you have any particular reason to use exactly the Visitor pattern? There are existent APIs that provide the ability to simply iterate over sub-descriptors (e.g., public val SerialDescriptor.elementDescriptors: Iterable<SerialDescriptor>
). In my [personal] opinion, the Visitor pattern is outdated now and should be replaced with FP operations on collections and iterables, such as map
or filter
+ when
over subtypes or kinds, when necessary. It results in a more concise and compact code with the same meaning — no need to override a bunch of different functions, code can be read top-down without additional navigation, etc. Your own SerialDescriptor.possibleSerializationSubclasses
showcases a good example of that. See also a similar ticket in kotlin-metadata-jvm: https://youtrack.jetbrains.com/issue/KT-59442
In any case, this seems like something that can be implemented on top of the kotlinx-serialization-core and even published as an additional utility library when necessary. So it is unlikely that such functionality will be added to the core, but it can be maintained by the community if there's a demand for that.
I don't have strong reason of using the visitor pattern. I just wanted something similar to encoders and decoders, to be sure to not forget anything, and to not .
The main idea behind it is not to have visitor pattern, but it's to have a standard way of going through the descriptors without a value (to make schemas, debug, generate reports, ...).
Visitor pattern just became naturally (I'm not that old! 😄). To be honest, at the beginning, I've just copy/pasted the Decoders interfaces and mainly removed the return types. After that, I just reverse-engineered the plugin generated serializers to well understand its workflow.
Btw, jackson library does this visitor stuff internally, and it allows users to easily implement new formats without having to think about how is descripted a map, a list, an class, etc
to have a standard way of going through the descriptors without a value (to make schemas, debug, generate reports, ...).
You can take a look at the protobuf schema generator, it uses SerialDescriptor.elementDescriptors()/getElementDescriptior
: https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator.kt#L142
Although this API is indeed not showcased anywhere. We likely should add a section to https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/formats.md#custom-formats-experimental with an explanation of how one can write a schema generator for a custom format.
We are currently using nearly all the same apis for generating schemas. In my opinion, a class of 500 lines to generate a schema is less readable than a well structured visitor pattern, and it's difficult to check quickly where is generated what depending on its kind or descriptor.
What is your use-case and why do you need this feature? There is no official way of reaching the descriptor tree.
In formats with schema like protobuf or avro, we need to read the full descriptors tree from the root serializer to generate the corresponding schemas. This logic could be provided by the kotlin serialization library to easily provide a way of reaching the descriptors.
Describe the solution you'd like Here is my current implementation. It has been made following the same concepts, and is customisable.
What we just need is to implement the different interfaces related to each descriptor's kind or key concepts:
SerialDescriptorValueVisitor
to visit a generic value. It's also the entrypoint for all the other visitorsSerialDescriptorMapVisitor
when kind isStructureKind.MAP
to visit its key and value descriptorsSerialDescriptorListVisitor
when kind isStructureKind.LIST
to visit its item descriptorSerialDescriptorPolymorphicVisitor
when kind isPolymorphicKind
to visit its implementation(s) descriptorsSerialDescriptorClassVisitor
when kind isStructureKind.CLASS
to visit its fields descriptorsSerialDescriptorInlineClassVisitor
whendescriptor.isInline
is true (same workflow asEncoder.encodeInline
)All the methods follow the same logic:
Unit
as we do not need to visit deeper.Here is an image showing all the interfaces and their methods:
And the code:
What do you think ? I can do a PR if needed