Skip to content

Instantly share code, notes, and snippets.

@Juraji
Created February 10, 2024 13:38
Show Gist options
  • Save Juraji/c66964198572903df744a222db81c053 to your computer and use it in GitHub Desktop.
Save Juraji/c66964198572903df744a222db81c053 to your computer and use it in GitHub Desktop.
Kotlin impl of deserializing FXCollections using Jackson (Module)
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
}
}
@Juraji
Copy link
Author

Juraji commented Feb 10, 2024

The ObservableSet deserializer doesn't work completely, but I just needed to put this somewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment