Created
February 10, 2024 13:38
-
-
Save Juraji/c66964198572903df744a222db81c053 to your computer and use it in GitHub Desktop.
Kotlin impl of deserializing FXCollections using Jackson (Module)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package nl.juraji.jackson.datatypes.fxcollections | |
| import com.fasterxml.jackson.annotation.JsonFormat | |
| import com.fasterxml.jackson.core.JsonParser | |
| import com.fasterxml.jackson.core.JsonToken | |
| import com.fasterxml.jackson.core.Version | |
| import com.fasterxml.jackson.core.json.PackageVersion | |
| import com.fasterxml.jackson.databind.* | |
| import com.fasterxml.jackson.databind.deser.ContextualDeserializer | |
| import com.fasterxml.jackson.databind.deser.ContextualKeyDeserializer | |
| import com.fasterxml.jackson.databind.deser.Deserializers | |
| import com.fasterxml.jackson.databind.deser.NullValueProvider | |
| import com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase | |
| import com.fasterxml.jackson.databind.jsontype.TypeDeserializer | |
| import com.fasterxml.jackson.databind.type.CollectionType | |
| import com.fasterxml.jackson.databind.type.MapType | |
| import com.fasterxml.jackson.databind.util.AccessPattern | |
| import javafx.collections.FXCollections | |
| import javafx.collections.ObservableList | |
| import javafx.collections.ObservableMap | |
| import javafx.collections.ObservableSet | |
| import tornadofx.* | |
| class FXCollectionsModule : Module() { | |
| override fun version(): Version = PackageVersion.VERSION | |
| override fun getModuleName(): String = "FXCollectionsModule" | |
| override fun setupModule(context: SetupContext) { | |
| context.addDeserializers(FXCollectionsDeserializers()) | |
| } | |
| } | |
| internal class FXCollectionsDeserializers : Deserializers.Base() { | |
| override fun findCollectionDeserializer( | |
| type: CollectionType, | |
| config: DeserializationConfig, | |
| beanDesc: BeanDescription, | |
| elementTypeDeserializer: TypeDeserializer?, | |
| elementDeserializer: JsonDeserializer<*>? | |
| ): JsonDeserializer<*>? { | |
| val raw = type.rawClass | |
| return if (ObservableList::class.java.isAssignableFrom(raw)) ObservableListDeserializer<Any>( | |
| type, null, false, elementDeserializer, elementTypeDeserializer | |
| ) | |
| else if (ObservableSet::class.java.isAssignableFrom(raw)) ObservableSetDeserializer<Any>( | |
| type, null, false, elementDeserializer, elementTypeDeserializer | |
| ) | |
| else null | |
| } | |
| override fun findMapDeserializer( | |
| type: MapType, | |
| config: DeserializationConfig, | |
| beanDesc: BeanDescription, | |
| keyDeserializer: KeyDeserializer?, | |
| elementTypeDeserializer: TypeDeserializer?, | |
| elementDeserializer: JsonDeserializer<*>? | |
| ): JsonDeserializer<*>? { | |
| val raw = type.rawClass | |
| return if (ObservableMap::class.java.isAssignableFrom(raw)) ObservableMapDeserializer<Any, Any>( | |
| type, null, keyDeserializer, elementDeserializer, elementTypeDeserializer | |
| ) | |
| else null | |
| } | |
| } | |
| @Suppress("UNCHECKED_CAST") | |
| internal abstract class ObservableCollectionDeserializer<T, C : MutableCollection<T>>( | |
| selfType: JavaType, | |
| nullValueProvider: NullValueProvider?, | |
| unwrapSingle: Boolean, | |
| private val valueDeserializer: JsonDeserializer<*>?, | |
| private val valueTypeDeserializer: TypeDeserializer? | |
| ) : ContainerDeserializerBase<C>(selfType, nullValueProvider, unwrapSingle), | |
| ContextualDeserializer { | |
| abstract fun createCollection(): C | |
| override fun createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer<*> { | |
| val unwrapSingle = findFormatFeature( | |
| ctxt, property, | |
| Collection::class.java, | |
| JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY | |
| ) ?: false | |
| val valueDeserializer = | |
| findConvertingContentDeserializer(ctxt, property, this.valueDeserializer) | |
| ?: ctxt.findContextualValueDeserializer(_containerType.contentType, property) | |
| val valueTypeDeserializer = this.valueTypeDeserializer?.forProperty(property) | |
| val nullValueProvider = findContentNullProvider(ctxt, property, valueDeserializer) | |
| if ( | |
| unwrapSingle != this._unwrapSingle | |
| || nullValueProvider != this._nullProvider | |
| || valueDeserializer != this.valueDeserializer | |
| || valueTypeDeserializer != this.valueTypeDeserializer | |
| ) return ObservableListDeserializer<T>( | |
| _containerType, | |
| nullValueProvider, | |
| unwrapSingle, | |
| valueDeserializer as JsonDeserializer<Any>, | |
| valueTypeDeserializer | |
| ) | |
| return this | |
| } | |
| override fun getContentDeserializer(): JsonDeserializer<Any> = | |
| this.valueDeserializer as JsonDeserializer<Any> | |
| override fun getEmptyAccessPattern(): AccessPattern = AccessPattern.CONSTANT | |
| override fun deserialize(p: JsonParser, ctxt: DeserializationContext): C { | |
| // Should usually point to START_ARRAY | |
| if (p.isExpectedStartArrayToken) { | |
| return deserializeContents(p, ctxt) | |
| } | |
| // But may support implicit arrays from single values? | |
| if (ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) { | |
| return deserializeFromSingleValue(p, ctxt) | |
| } | |
| return ctxt.handleUnexpectedToken(_valueClass, p) as C | |
| } | |
| private fun deserializeContents(p: JsonParser, ctxt: DeserializationContext): C { | |
| var t: JsonToken = p.nextToken() | |
| val target = createCollection() | |
| while (t != JsonToken.END_ARRAY) { | |
| val value: Any = | |
| if (t == JsonToken.VALUE_NULL && _skipNullValues) continue | |
| else if (t == JsonToken.VALUE_NULL) _nullProvider.getNullValue(ctxt) | |
| else if (valueTypeDeserializer == null) valueDeserializer!!.deserialize(p, ctxt) | |
| else valueDeserializer!!.deserializeWithType(p, ctxt, valueTypeDeserializer) | |
| target.add(value as T) | |
| t = p.nextToken() | |
| } | |
| return target | |
| } | |
| private fun deserializeFromSingleValue(p: JsonParser, ctxt: DeserializationContext): C { | |
| val t = p.currentToken | |
| if (_skipNullValues && t == JsonToken.VALUE_NULL) | |
| return createCollection() | |
| val value: Any = | |
| if (t == JsonToken.VALUE_NULL) _nullProvider.getNullValue(ctxt) | |
| else if (valueTypeDeserializer == null) valueDeserializer!!.deserialize(p, ctxt) | |
| else valueDeserializer!!.deserializeWithType(p, ctxt, valueTypeDeserializer) | |
| return createCollection().apply { add(value as T) } | |
| } | |
| } | |
| internal class ObservableListDeserializer<T>( | |
| selfType: JavaType, | |
| nullValueProvider: NullValueProvider?, | |
| unwrapSingle: Boolean, | |
| valueDeserializer: JsonDeserializer<*>?, | |
| valueTypeDeserializer: TypeDeserializer? | |
| ) : ObservableCollectionDeserializer<T, ObservableList<T>>( | |
| selfType, | |
| nullValueProvider, | |
| unwrapSingle, | |
| valueDeserializer, | |
| valueTypeDeserializer | |
| ) { | |
| override fun createCollection(): ObservableList<T> = FXCollections.observableArrayList() | |
| } | |
| internal class ObservableSetDeserializer<T>( | |
| selfType: JavaType, | |
| nullValueProvider: NullValueProvider?, | |
| unwrapSingle: Boolean, | |
| valueDeserializer: JsonDeserializer<*>?, | |
| valueTypeDeserializer: TypeDeserializer? | |
| ) : ObservableCollectionDeserializer<T, ObservableSet<T>>( | |
| selfType, | |
| nullValueProvider, | |
| unwrapSingle, | |
| valueDeserializer, | |
| valueTypeDeserializer | |
| ) { | |
| override fun createCollection(): ObservableSet<T> = FXCollections.observableSet() | |
| } | |
| @Suppress("UNCHECKED_CAST") | |
| internal class ObservableMapDeserializer<K, V>( | |
| selfType: JavaType, | |
| nullValueProvider: NullValueProvider?, | |
| private val keyDeserializer: KeyDeserializer?, | |
| private val valueDeserializer: JsonDeserializer<*>?, | |
| private val valueTypeDeserializer: TypeDeserializer? | |
| ) : ContainerDeserializerBase<ObservableMap<K, V>>(selfType, nullValueProvider, false), | |
| ContextualDeserializer { | |
| override fun createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer<*> { | |
| val keyDeserializer: KeyDeserializer = (this.keyDeserializer | |
| ?: ctxt.findKeyDeserializer(_containerType.keyType, property)) | |
| .let { | |
| if (it is ContextualKeyDeserializer) it.createContextual(ctxt, property) | |
| else it | |
| } | |
| val valueDeserializer = | |
| findConvertingContentDeserializer(ctxt, property, this.valueDeserializer) | |
| ?: ctxt.findContextualValueDeserializer(_containerType.contentType, property) | |
| val valueTypeDeserializer = this.valueTypeDeserializer?.forProperty(property) | |
| val nullValueProvider = findContentNullProvider(ctxt, property, valueDeserializer) | |
| if ( | |
| nullValueProvider != this._nullProvider | |
| || keyDeserializer != this.keyDeserializer | |
| || valueDeserializer != this.valueDeserializer | |
| || valueTypeDeserializer != this.valueTypeDeserializer | |
| ) return ObservableMapDeserializer<K, V>( | |
| _containerType, | |
| nullValueProvider, | |
| keyDeserializer, | |
| valueDeserializer, | |
| valueTypeDeserializer | |
| ) | |
| return this | |
| } | |
| override fun getContentDeserializer(): JsonDeserializer<Any> = | |
| this.valueDeserializer as JsonDeserializer<Any> | |
| override fun getEmptyAccessPattern(): AccessPattern = AccessPattern.CONSTANT | |
| override fun deserialize(p: JsonParser, ctxt: DeserializationContext): ObservableMap<K, V> { | |
| // Ok: must point to START_OBJECT or FIELD_NAME | |
| var t = p.currentToken | |
| // If START_OBJECT, move to next; may also be END_OBJECT | |
| if (t == JsonToken.START_OBJECT) t = p.nextToken() | |
| if (t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) | |
| return ctxt.handleUnexpectedToken(_containerType.rawClass, p) as ObservableMap<K, V> | |
| return deserializeEntries(p, ctxt) | |
| } | |
| private fun deserializeEntries(p: JsonParser, ctxt: DeserializationContext): ObservableMap<K, V> { | |
| val target = observableMapOf<K, V>() | |
| while (p.currentToken === JsonToken.FIELD_NAME) { | |
| val fieldName = p.currentName() | |
| val key: Any = | |
| if ((keyDeserializer == null)) fieldName | |
| else keyDeserializer.deserializeKey(fieldName, ctxt) | |
| val t = p.nextToken() | |
| var value: Any? | |
| if (t == JsonToken.VALUE_NULL) { | |
| if (!_skipNullValues) { | |
| value = _nullProvider.getNullValue(ctxt) | |
| if (value != null) { | |
| target[key as K] = value as V | |
| } | |
| } | |
| p.nextToken() | |
| continue | |
| } | |
| value = | |
| if (valueTypeDeserializer == null) valueDeserializer!!.deserialize(p, ctxt) | |
| else valueDeserializer!!.deserializeWithType(p, ctxt, valueTypeDeserializer) | |
| target[key as K] = value as V | |
| p.nextToken() | |
| } | |
| return target | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The ObservableSet deserializer doesn't work completely, but I just needed to put this somewhere.