All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.http4k.format.ConfigurableMoshi.kt Maven / Gradle / Ivy

There is a newer version: 5.35.1.0
Show newest version
package org.http4k.format

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import okio.buffer
import okio.source
import org.http4k.core.Body
import org.http4k.core.ContentType
import org.http4k.core.ContentType.Companion.APPLICATION_JSON
import org.http4k.core.HttpMessage
import org.http4k.core.with
import org.http4k.format.StrictnessMode.FailOnUnknown
import org.http4k.format.StrictnessMode.Lenient
import org.http4k.lens.BiDiBodyLensSpec
import org.http4k.lens.BiDiMapping
import org.http4k.lens.BiDiWsMessageLensSpec
import org.http4k.lens.ContentNegotiation
import org.http4k.lens.ContentNegotiation.Companion.None
import org.http4k.lens.string
import org.http4k.websocket.WsMessage
import java.io.InputStream
import java.math.BigDecimal
import java.math.BigInteger
import kotlin.reflect.KClass

open class ConfigurableMoshi(
    builder: Moshi.Builder,
    override val defaultContentType: ContentType = APPLICATION_JSON,
    private val strictness: StrictnessMode = Lenient
) : AutoMarshallingJson() {

    private val moshi = builder.build()

    private val objectAdapter = moshi.adapter(Any::class.java)

    override fun MoshiNode.asPrettyJsonString(): String = objectAdapter.indent("    ").toJson(unwrap())
    override fun MoshiNode.asCompactJsonString(): String = objectAdapter.toJson(unwrap())

    override fun String.asJsonObject() = MoshiNode.wrap(objectAdapter.fromJson(this))

    override fun >> LIST.asJsonObject() = MoshiObject(toMap())
    override fun String?.asJsonValue() = if (this == null) MoshiNull else MoshiString(this)
    override fun Int?.asJsonValue() = if (this == null) MoshiNull else MoshiInteger(toLong())
    override fun Double?.asJsonValue() = if (this == null) MoshiNull else MoshiDecimal(this)
    override fun Long?.asJsonValue() = if (this == null) MoshiNull else MoshiInteger(this)
    override fun BigDecimal?.asJsonValue() = if (this == null) MoshiNull else MoshiDecimal(toDouble())
    override fun BigInteger?.asJsonValue() = if (this == null) MoshiNull else MoshiInteger(toLong())
    override fun Boolean?.asJsonValue() = if (this == null) MoshiNull else MoshiBoolean(this)
    override fun > T.asJsonArray() = MoshiArray(toList())

    override fun textValueOf(node: MoshiNode, name: String): String? = (node as? MoshiObject)
        ?.attributes?.get(name)
        ?.unwrap()?.toString()

    override fun decimal(value: MoshiNode) = (value as MoshiDecimal).value.toBigDecimal()
    override fun integer(value: MoshiNode) = ((value as MoshiInteger).value)
    override fun bool(value: MoshiNode) = (value as MoshiBoolean).value
    override fun text(value: MoshiNode) = when (value) {
        is MoshiString -> value.value
        is MoshiBoolean -> value.value.toString()
        is MoshiInteger -> value.value.toString()
        is MoshiDecimal -> value.value.toString()
        is MoshiArray -> ""
        is MoshiObject -> ""
        MoshiNull -> "null"
    }

    override fun elements(value: MoshiNode) = (value as MoshiArray).elements
    override fun fields(node: MoshiNode) = (node as? MoshiObject)
        ?.attributes
        ?.map { it.key to it.value }
        ?: emptyList()

    override fun typeOf(value: MoshiNode) = when (value) {
        is MoshiNull -> JsonType.Null
        is MoshiObject -> JsonType.Object
        is MoshiArray -> JsonType.Array
        is MoshiInteger -> JsonType.Integer
        is MoshiDecimal -> JsonType.Number
        is MoshiString -> JsonType.String
        is MoshiBoolean -> JsonType.Boolean
    }

    override fun asFormatString(input: Any): String = moshi.adapter(input.javaClass).toJson(input)

    fun  asJsonString(t: T, c: KClass): String = moshi.adapter(c.java).toJson(t)

    override fun  asA(input: String, target: KClass): T = adapterFor(target).fromJson(input)!!

    private fun  adapterFor(target: KClass) = when (strictness) {
        Lenient -> moshi.adapter(target.java)
        FailOnUnknown -> moshi.adapter(target.java).failOnUnknown()
    }

    override fun  asA(input: InputStream, target: KClass): T = adapterFor(target).fromJson(
        input.source().buffer()
    )!!

    /**
     * Convenience function to write the object as JSON to the message body and set the content type.
     */
    inline fun  R.json(t: T): R = with(Body.auto().toLens() of t)

    /**
     * Convenience function to read an object as JSON from the message body.
     */
    inline fun  HttpMessage.json(): T = Body.auto().toLens()(this)

    override fun asJsonObject(input: Any): MoshiNode = MoshiNode.wrap(objectAdapter.toJsonValue(input))

    override fun  asA(j: MoshiNode, target: KClass): T = adapterFor(target)
        .fromJsonValue(j.unwrap())!!

    inline fun  asBiDiMapping() =
        BiDiMapping({ asA(it, T::class) }, { asFormatString(it) })

    inline fun  Body.Companion.auto(
        description: String? = null,
        contentNegotiation: ContentNegotiation = None,
        contentType: ContentType = defaultContentType
    ): BiDiBodyLensSpec =
        autoBody(description, contentNegotiation, contentType)

    inline fun  autoBody(
        description: String? = null,
        contentNegotiation: ContentNegotiation = None,
        contentType: ContentType = defaultContentType
    ): BiDiBodyLensSpec =
        Body.string(contentType, description, contentNegotiation).map({ asA(it, T::class) }, {
            asFormatString(it)
        })

    inline fun  WsMessage.Companion.auto(): BiDiWsMessageLensSpec =
        WsMessage.string().map({ it.asA(T::class) }, { asFormatString(it) })
}

fun Moshi.Builder.asConfigurable(
    kotlinFactory: JsonAdapter.Factory = KotlinJsonAdapterFactory()
) = object : AutoMappingConfiguration {
    override fun  int(mapping: BiDiMapping) = adapter(mapping, { value(it) }, { nextInt() })
    override fun  long(mapping: BiDiMapping) =
        adapter(mapping, { value(it) }, { nextLong() })

    override fun  double(mapping: BiDiMapping) =
        adapter(mapping, { value(it) }, { nextDouble() })

    override fun  bigInteger(mapping: BiDiMapping) =
        adapter(mapping, { value(it) }, { nextLong().toBigInteger() })

    override fun  bigDecimal(mapping: BiDiMapping) =
        adapter(mapping, { value(it) }, { nextDouble().toBigDecimal() })

    override fun  boolean(mapping: BiDiMapping) =
        adapter(mapping, { value(it) }, { nextBoolean() })

    override fun  text(mapping: BiDiMapping) =
        adapter(mapping, { value(it) }, { nextString() })

    private fun  adapter(
        mapping: BiDiMapping,
        write: JsonWriter.(IN) -> Unit,
        read: JsonReader.() -> IN
    ) =
        apply {
            add(mapping.clazz, object : JsonAdapter() {
                override fun fromJson(reader: JsonReader) = mapping.invoke(reader.read())

                override fun toJson(writer: JsonWriter, value: OUT?) {
                    value?.let { writer.write(mapping(it)) } ?: writer.nullValue()
                }
            }.nullSafe())
        }

    // add the Kotlin adapter last, as it will hjiack our custom mappings otherwise
    override fun done() = this@asConfigurable
        .add(Unit::class.java, UnitAdapter)
        .addLast(kotlinFactory)
}

private object UnitAdapter : JsonAdapter() {
    override fun fromJson(reader: JsonReader) {
        reader.readJsonValue(); Unit
    }

    override fun toJson(writer: JsonWriter, value: Unit?) {
        value?.let { writer.beginObject().endObject() } ?: writer.nullValue()
    }
}

inline operator fun  ConfigurableMoshi.invoke(msg: HttpMessage): T = autoBody().toLens()(msg)
inline operator fun  ConfigurableMoshi.invoke(item: T) = autoBody().toLens().of(item)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy