org.http4k.format.ConfigurableJackson.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of http4k-format-jackson Show documentation
Show all versions of http4k-format-jackson Show documentation
Http4k Jackson JSON/XML support
package org.http4k.format
import com.fasterxml.jackson.core.JsonParser.NumberType.BIG_INTEGER
import com.fasterxml.jackson.core.JsonParser.NumberType.INT
import com.fasterxml.jackson.core.JsonParser.NumberType.LONG
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.BigIntegerNode
import com.fasterxml.jackson.databind.node.BooleanNode
import com.fasterxml.jackson.databind.node.DecimalNode
import com.fasterxml.jackson.databind.node.NullNode
import com.fasterxml.jackson.databind.node.NumericNode
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.databind.node.TextNode
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.convertValue
import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
import com.fasterxml.jackson.module.kotlin.readValue
import io.cloudevents.core.builder.CloudEventBuilder
import io.cloudevents.jackson.JsonCloudEventData
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.JsonType.Integer
import org.http4k.format.JsonType.Number
import org.http4k.lens.BiDiBodyLensSpec
import org.http4k.lens.BiDiMapping
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 ConfigurableJackson(
val mapper: ObjectMapper,
override val defaultContentType: ContentType = APPLICATION_JSON
) : AutoMarshallingJson() {
override fun typeOf(value: JsonNode): JsonType = when (value) {
is TextNode -> JsonType.String
is BooleanNode -> JsonType.Boolean
is NumericNode -> when (value.numberType()) {
INT, LONG, BIG_INTEGER -> Integer
else -> Number
}
is ArrayNode -> JsonType.Array
is ObjectNode -> JsonType.Object
is NullNode -> JsonType.Null
else -> throw IllegalArgumentException("Don't know how to translate $value")
}
override fun String.asJsonObject(): JsonNode = mapper.readValue(this, JsonNode::class.java)
override fun String?.asJsonValue(): JsonNode = this?.let { TextNode(this) } ?: NullNode.instance
override fun Int?.asJsonValue(): JsonNode = this?.let { BigIntegerNode(toBigInteger()) } ?: NullNode.instance
override fun Double?.asJsonValue(): JsonNode = this?.let { DecimalNode(BigDecimal(this)) } ?: NullNode.instance
override fun Long?.asJsonValue(): JsonNode = this?.let { BigIntegerNode(toBigInteger()) } ?: NullNode.instance
override fun BigDecimal?.asJsonValue(): JsonNode = this?.let { DecimalNode(this) } ?: NullNode.instance
override fun BigInteger?.asJsonValue(): JsonNode = this?.let { BigIntegerNode(this) } ?: NullNode.instance
override fun Boolean?.asJsonValue(): JsonNode = this?.let { BooleanNode.valueOf(this) } ?: NullNode.instance
override fun > T.asJsonArray(): JsonNode =
mapper.createArrayNode().also { it.addAll(toList()) }
override fun JsonNode.asPrettyJsonString(): String =
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this)
override fun JsonNode.asCompactJsonString(): String = mapper.writeValueAsString(this)
override fun >> LIST.asJsonObject(): JsonNode =
mapper.createObjectNode().also { it.setAll(toList().toMap()) }
override fun fields(node: JsonNode) = node.fields().asSequence().map { (key, value) -> key to value }.toList()
override fun elements(value: JsonNode) = value.elements().asSequence().asIterable()
override fun text(value: JsonNode): String = value.asText()
override fun bool(value: JsonNode): Boolean = value.asBoolean()
override fun integer(value: JsonNode) = value.asLong()
override fun decimal(value: JsonNode) = BigDecimal(value.toString())
override fun textValueOf(node: JsonNode, name: String) = node[name]?.asText()
// auto
override fun asJsonObject(input: Any): JsonNode = mapper.convertValue(input, JsonNode::class.java)
override fun asA(input: String, target: KClass): T = mapper.readValue(input, target.java)
override fun asA(j: JsonNode, target: KClass): T = mapper.convertValue(j, target.java)
override fun asA(input: InputStream, target: KClass): T = mapper.readValue(input, target.java)
inline fun JsonNode.asA(): T = mapper.convertValue(this)
override fun asInputStream(input: Any): InputStream = mapper.writeValueAsBytes(input).inputStream()
inline fun WsMessage.Companion.auto() = WsMessage.string().map(mapper.read(), mapper.write())
inline fun asBiDiMapping() = BiDiMapping(mapper.read(), mapper.write())
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
): BiDiBodyLensSpec =
httpBodyLens(description, contentNegotiation, contentType).map(mapper.read(), mapper.write())
/**
* 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)
// views
fun T.asCompactJsonStringUsingView(v: KClass): String =
mapper.writerWithView(v.java).writeValueAsString(this)
fun String.asUsingView(t: KClass, v: KClass): T =
mapper.readerWithView(v.java).forType(t.java).readValue(this)
inline fun Body.Companion.autoView(
description: String? = null,
contentNegotiation: ContentNegotiation = None,
contentType: ContentType = APPLICATION_JSON
) = Body.string(contentType, description, contentNegotiation)
.map({ it.asUsingView(T::class, V::class) }, { it.asCompactJsonStringUsingView(V::class) })
inline fun WsMessage.Companion.autoView() =
WsMessage.string().map({ it.asUsingView(T::class, V::class) }, { it.asCompactJsonStringUsingView(V::class) })
fun CloudEventBuilder.withData(t: T) =
withData(defaultContentType.value, JsonCloudEventData.wrap(asJsonObject(t)))
}
fun KotlinModule.asConfigurable() = asConfigurable(ObjectMapper())
inline fun ObjectMapper.read(): (String) -> T = { readValue(it) }
inline fun ObjectMapper.write(): (T) -> String = {
with(this) {
val typeRef = jacksonTypeRef()
when {
typeFactory.constructType(typeRef).isContainerType -> writer().forType(typeRef).writeValueAsString(it)
else -> writeValueAsString(it)
}
}
}
inline operator fun ConfigurableJackson.invoke(msg: HttpMessage): T = autoBody().toLens()(msg)
inline operator fun ConfigurableJackson.invoke(item: T) =
autoBody().toLens().of(item)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy