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

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

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

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
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.lens.BiDiMapping
import org.http4k.lens.ContentNegotiation
import org.http4k.lens.ContentNegotiation.Companion.None
import org.http4k.websocket.WsMessage
import java.io.InputStream
import java.lang.reflect.Type
import java.math.BigDecimal
import java.math.BigInteger
import kotlin.reflect.KClass

open class ConfigurableGson(
    builder: GsonBuilder,
    override val defaultContentType: ContentType = APPLICATION_JSON
) : AutoMarshallingJson() {

    val mapper: Gson = builder.create()
    private val pretty = builder.setPrettyPrinting().create()

    override fun typeOf(value: JsonElement): JsonType =
        when {
            value.isJsonArray -> JsonType.Array
            value.isJsonNull -> JsonType.Null
            value.isJsonObject -> JsonType.Object
            value.isJsonPrimitive -> with(value.asJsonPrimitive) {
                when {
                    isBoolean -> JsonType.Boolean
                    isNumber -> if (value.asString.any { !it.isDigit() }) JsonType.Number else JsonType.Integer
                    isString -> JsonType.String
                    else -> throw IllegalArgumentException("Don't know how to translate $value")
                }
            }

            else -> throw IllegalArgumentException("Don't know how to translate $value")
        }

    override fun String.asJsonObject(): JsonElement = JsonParser.parseString(this).let {
        if (it.isJsonArray || it.isJsonObject) it else throw InvalidJsonException(
            "Could not convert to a JSON Object or Array. $this"
        )
    }

    inline fun  R.with(t: T): R = with(Body.auto().toLens() of t)

    override fun String?.asJsonValue(): JsonElement = this?.let { JsonPrimitive(this) } ?: JsonNull.INSTANCE
    override fun Int?.asJsonValue(): JsonElement = this?.let { JsonPrimitive(this) } ?: JsonNull.INSTANCE
    override fun Double?.asJsonValue(): JsonElement = this?.let { JsonPrimitive(this) } ?: JsonNull.INSTANCE
    override fun Long?.asJsonValue(): JsonElement = this?.let { JsonPrimitive(this) } ?: JsonNull.INSTANCE
    override fun BigDecimal?.asJsonValue(): JsonElement = this?.let { JsonPrimitive(this) } ?: JsonNull.INSTANCE
    override fun BigInteger?.asJsonValue(): JsonElement = this?.let { JsonPrimitive(this) } ?: JsonNull.INSTANCE
    override fun Boolean?.asJsonValue(): JsonElement = this?.let { JsonPrimitive(this) } ?: JsonNull.INSTANCE
    override fun > T.asJsonArray(): JsonElement =
        fold(JsonArray()) { memo, o -> memo.add(o); memo }

    override fun JsonElement.asPrettyJsonString(): String = pretty.toJson(this)
    override fun JsonElement.asCompactJsonString(): String = mapper.toJson(this)
    override fun >> LIST.asJsonObject() =
        JsonObject().apply { forEach { add(it.first, it.second) } }

    override fun fields(node: JsonElement): Iterable> =
        if (typeOf(node) != JsonType.Object) emptyList() else {
            val fieldList = mutableListOf>()
            for ((key, value) in node.asJsonObject.entrySet()) {
                fieldList += key to value
            }
            fieldList
        }

    override fun elements(value: JsonElement): Iterable = value.asJsonArray
    override fun text(value: JsonElement): String = if (value is JsonNull) "null" else value.asString
    override fun bool(value: JsonElement): Boolean = value.asBoolean
    override fun integer(value: JsonElement) = value.asLong
    override fun decimal(value: JsonElement): BigDecimal = value.asBigDecimal
    override fun textValueOf(node: JsonElement, name: String): String = when (node) {
        is JsonObject -> node[name].asString
        else -> throw IllegalArgumentException("node is not an object")
    }

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

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

    // auto
    override fun asJsonObject(input: Any): JsonElement = mapper.toJsonTree(input)

    override fun  asA(input: String, target: KClass): T = mapper.fromJson(input, target.java)

    override fun  asA(input: InputStream, target: KClass): T =
        mapper.fromJson(JsonReader(input.reader()), target.java)

    override fun  asA(j: JsonElement, target: KClass): T = mapper.fromJson(j, target.java)

    inline fun  JsonElement.asA(): T = mapper.fromJson(this, object : TypeToken() {}.type)

    inline fun  WsMessage.Companion.auto() =
        WsMessage.json().map({ it.asA() }, { it.asJsonObject() })

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

    inline fun  autoBody(
        description: String? = null,
        contentNegotiation: ContentNegotiation = None,
        contentType: ContentType = defaultContentType
    ) =
        httpBodyLens(description, contentNegotiation, contentType).map(mapper.read()) { mapper.toJson(it) }
}

inline fun  Gson.read(): (String) -> T =
    { fromJson(it, object : TypeToken() {}.type) as T }

class InvalidJsonException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)

fun GsonBuilder.asConfigurable() = object : AutoMappingConfiguration {
    override fun  int(mapping: BiDiMapping) = adapter(mapping, ::JsonPrimitive, JsonElement::getAsInt)
    override fun  long(mapping: BiDiMapping) = adapter(mapping, ::JsonPrimitive, JsonElement::getAsLong)
    override fun  double(mapping: BiDiMapping) =
        adapter(mapping, ::JsonPrimitive, JsonElement::getAsDouble)

    override fun  boolean(mapping: BiDiMapping) =
        adapter(mapping, ::JsonPrimitive, JsonElement::getAsBoolean)

    override fun  bigInteger(mapping: BiDiMapping) =
        adapter(mapping, ::JsonPrimitive, JsonElement::getAsBigInteger)

    override fun  bigDecimal(mapping: BiDiMapping) =
        adapter(mapping, ::JsonPrimitive, JsonElement::getAsBigDecimal)

    override fun  text(mapping: BiDiMapping) =
        adapter(mapping, ::JsonPrimitive, JsonElement::getAsString)

    private fun  adapter(
        mapping: BiDiMapping,
        asPrimitive: IN.() -> JsonPrimitive,
        value: JsonElement.() -> IN
    ) =
        apply {
            [email protected](mapping.clazz, object : JsonSerializer, JsonDeserializer {
                override fun serialize(src: OUT, typeOfSrc: Type, context: JsonSerializationContext) =
                    mapping(src).asPrimitive()

                override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext) =
                    mapping.invoke(json.value())
            })
        }

    override fun done(): GsonBuilder = this@asConfigurable
}

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy