diff --git a/build.gradle.kts b/build.gradle.kts index 5cd073fd..ce501a6e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -97,11 +97,8 @@ dependencies { // Content validation implementation("org.apache.tika:tika-core:3.3.1") - // Import: DateTime Parsing - implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.22.1") - - // Serialization - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.22.1") + // Serialization (Jackson 3; JSR-310 support is built into jackson-databind 3.x) + implementation("tools.jackson.module:jackson-module-kotlin:3.1.4") // Testing testImplementation("io.kotest:kotest-runner-junit5-jvm:6.2.2") diff --git a/src/main/java/ext/Json.kt b/src/main/java/ext/Json.kt index f35efcfe..3dc0af5e 100644 --- a/src/main/java/ext/Json.kt +++ b/src/main/java/ext/Json.kt @@ -1,22 +1,27 @@ package ext import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.core.JsonToken -import com.fasterxml.jackson.databind.* -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNamingStrategy +import tools.jackson.core.JsonParser +import tools.jackson.core.JsonToken +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.DeserializationFeature +import tools.jackson.databind.ObjectMapper +import tools.jackson.databind.PropertyNamingStrategies +import tools.jackson.databind.ValueDeserializer +import tools.jackson.databind.ext.javatime.ser.LocalDateTimeSerializer +import tools.jackson.databind.json.JsonMapper +import tools.jackson.databind.module.SimpleModule +import tools.jackson.module.kotlin.KotlinModule import java.time.LocalDateTime import java.time.format.DateTimeFormatter // Jackson val ACCEPTABLE_FALSE = setOf("0", "false", "no", "off", "False", "None", "null") val ACCEPTABLE_TRUE = setOf("1", "true", "yes", "on", "True") -val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, object : JsonDeserializer() { +val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, object : ValueDeserializer() { override fun deserialize(parser: JsonParser, context: DeserializationContext) = when(parser.text) { in ACCEPTABLE_FALSE -> false in ACCEPTABLE_TRUE -> true @@ -25,7 +30,7 @@ val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, obj }) val JSON_DATETIME = SimpleModule() .addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME)) - .addDeserializer(LocalDateTime::class.java, object : JsonDeserializer() { + .addDeserializer(LocalDateTime::class.java, object : ValueDeserializer() { override fun deserialize(parser: JsonParser, context: DeserializationContext): LocalDateTime? { parser.readLocalDateTimeArray(context)?.let { return it } @@ -45,8 +50,7 @@ fun JsonParser.readLocalDateTimeArray(context: DeserializationContext): LocalDat val parts = mutableListOf() while (nextToken() != JsonToken.END_ARRAY) { if (currentToken() != JsonToken.VALUE_NUMBER_INT) { - throw context.weirdStringException( - parts.toString(), + return context.reportInputMismatch( LocalDateTime::class.java, "Invalid legacy date-time array", ) @@ -55,8 +59,7 @@ fun JsonParser.readLocalDateTimeArray(context: DeserializationContext): LocalDat } if (parts.size !in 5..7) { - throw context.weirdStringException( - parts.toString(), + return context.reportInputMismatch( LocalDateTime::class.java, "Invalid legacy date-time array; expected 5 to 7 integer components", ) @@ -68,21 +71,25 @@ fun JsonParser.readLocalDateTimeArray(context: DeserializationContext): LocalDat parts.getOrElse(5) { 0 }, parts.getOrElse(6) { 0 }, ) } catch (e: Exception) { - throw context.weirdStringException( - parts.toString(), + context.reportInputMismatch( LocalDateTime::class.java, "Invalid legacy date-time array: ${e.message}", ) } } -val JACKSON = jacksonObjectMapper().apply { - setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL) - findAndRegisterModules() - registerModule(JSON_FUZZY_BOOLEAN) - registerModule(JSON_DATETIME) - configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - propertyNamingStrategy = PropertyNamingStrategies.LOWER_CAMEL_CASE; -} + +val JACKSON: JsonMapper = JsonMapper.builder() + .addModule(KotlinModule.Builder().build()) + .findAndAddModules() + .addModule(JSON_FUZZY_BOOLEAN) + .addModule(JSON_DATETIME) + .changeDefaultPropertyInclusion { + JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL) + } + .propertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build() + inline fun ObjectMapper.parse(str: Str) = readValue(str, T::class.java) inline fun ObjectMapper.parse(map: Map<*, *>) = convertValue(map, T::class.java) // TODO: https://stackoverflow.com/q/78197784/7346633 @@ -117,11 +124,18 @@ val JSON = Json { coerceInputValues = true } -// Bean for default jackson object mapper -//@Configuration -//class JacksonConfig { -// @Bean -// fun objectMapper(): ObjectMapper { -// return JACKSON -// } -//} +/** + * Expose the shared Jackson 3 mapper as the application JsonMapper. + * + * Spring Boot 4 auto-configures a tools.jackson JsonMapper WITHOUT the Kotlin module + * (Jackson2ObjectMapperBuilder's well-known-module registration no longer exists), + * which silently renamed all `is*` boolean fields in @RestController responses + * (e.g. isClear -> clear) and ignored Jackson 2 databind annotations. + * Providing our own JsonMapper bean makes Boot's JacksonAutoConfiguration back off + * (@ConditionalOnMissingBean), so the HTTP message converters use this mapper. + */ +@org.springframework.context.annotation.Configuration +class JacksonConfig { + @org.springframework.context.annotation.Bean + fun jsonMapper(): JsonMapper = JACKSON +} diff --git a/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt b/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt index 86079cb0..244df8f2 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt @@ -1,6 +1,6 @@ package icu.samnyan.aqua.net.games -import com.fasterxml.jackson.core.JsonProcessingException +import tools.jackson.core.JacksonException import ext.* import icu.samnyan.aqua.net.Fedy import icu.samnyan.aqua.net.db.AquaNetUser @@ -124,7 +124,7 @@ abstract class ImportController, UserModel: json.parseJackson(exportClass.java) } catch (e: Exception) { val jsonError = generateSequence(e) { it.cause } - .filterIsInstance() + .filterIsInstance() .firstOrNull() ?: throw e diff --git a/src/main/java/icu/samnyan/aqua/net/games/ImportHelper.kt b/src/main/java/icu/samnyan/aqua/net/games/ImportHelper.kt index 39b6651a..15056d12 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/ImportHelper.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/ImportHelper.kt @@ -1,9 +1,9 @@ package icu.samnyan.aqua.net.games -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.module.SimpleModule +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.ValueDeserializer +import tools.jackson.databind.module.SimpleModule import ext.JACKSON import ext.minus @@ -31,7 +31,7 @@ fun String.asSqlInsert(): SqlInsert { } @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST") -val JSON_INT_LIST_STR = SimpleModule().addDeserializer(List::class.java, object : JsonDeserializer>() { +val JSON_INT_LIST_STR = SimpleModule().addDeserializer(List::class.java, object : ValueDeserializer>() { override fun deserialize(parser: JsonParser, context: DeserializationContext) = try { val text = parser.text.trim('[', ']') @@ -41,6 +41,4 @@ val JSON_INT_LIST_STR = SimpleModule().addDeserializer(List::class.java, object 400 - "Invalid list value ${parser.text}: $e" } }) -val JACKSON_ARTEMIS = JACKSON.copy().apply { - registerModule(JSON_INT_LIST_STR) -} \ No newline at end of file +val JACKSON_ARTEMIS = JACKSON.rebuild().addModule(JSON_INT_LIST_STR).build() \ No newline at end of file diff --git a/src/main/java/icu/samnyan/aqua/sega/cardmaker/CardMakerControllerAdvice.kt b/src/main/java/icu/samnyan/aqua/sega/cardmaker/CardMakerControllerAdvice.kt index 17a882a0..2b8edfbe 100644 --- a/src/main/java/icu/samnyan/aqua/sega/cardmaker/CardMakerControllerAdvice.kt +++ b/src/main/java/icu/samnyan/aqua/sega/cardmaker/CardMakerControllerAdvice.kt @@ -1,7 +1,7 @@ package icu.samnyan.aqua.sega.cardmaker -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.ObjectMapper +import tools.jackson.core.type.TypeReference +import tools.jackson.databind.json.JsonMapper import ext.logger import jakarta.servlet.http.HttpServletRequest import org.springframework.web.bind.annotation.ModelAttribute @@ -14,7 +14,7 @@ import java.nio.charset.StandardCharsets @RestControllerAdvice(basePackages = ["icu.samnyan.aqua.sega.cardmaker"]) class CardMakerControllerAdvice { val logger = logger() - val mapper = ObjectMapper() + val mapper = JsonMapper() /** * Get the map object from json string diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserData.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserData.kt index 1fadea15..5745c368 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserData.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserData.kt @@ -3,11 +3,11 @@ package icu.samnyan.aqua.sega.chusan.model.userdata import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.ValueDeserializer +import tools.jackson.databind.annotation.JsonDeserialize +import tools.jackson.databind.annotation.JsonSerialize import ext.readLocalDateTimeArray import icu.samnyan.aqua.net.games.BaseEntity import icu.samnyan.aqua.net.games.IUserData @@ -22,7 +22,7 @@ import java.time.format.DateTimeFormatterBuilder import java.time.format.ResolverStyle import java.time.temporal.ChronoField -class FlexibleDateTimeDeserializer : JsonDeserializer() { +class FlexibleDateTimeDeserializer : ValueDeserializer() { @Throws(IOException::class) public override fun deserialize(p: JsonParser, ctxt: DeserializationContext): LocalDateTime { p.readLocalDateTimeArray(ctxt)?.let { return it } @@ -30,10 +30,10 @@ class FlexibleDateTimeDeserializer : JsonDeserializer() { val value = p.text return FORMATTERS.firstNotNullOfOrNull { formatter -> runCatching { LocalDateTime.parse(value, formatter) }.getOrNull() - } ?: throw ctxt.weirdStringException( - value, + } ?: ctxt.reportInputMismatch( LocalDateTime::class.java, - "Invalid date-time; expected yyyy-MM-dd'T'HH:mm:ss or yyyy-MM-dd HH:mm:ss with an optional fractional second", + "Invalid date-time '%s'; expected yyyy-MM-dd'T'HH:mm:ss or yyyy-MM-dd HH:mm:ss with an optional fractional second", + value, ) } diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/UserMusicDetail.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/UserMusicDetail.kt index edc5e5de..aa7377c8 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/UserMusicDetail.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/UserMusicDetail.kt @@ -1,7 +1,7 @@ package icu.samnyan.aqua.sega.chusan.model.userdata import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import tools.jackson.databind.annotation.JsonDeserialize import icu.samnyan.aqua.net.games.IGenericUserMusic import icu.samnyan.aqua.sega.util.BooleanToIntegerDeserializer import jakarta.persistence.Entity diff --git a/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaMapper.kt b/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaMapper.kt index f4f5bec4..10dfcd63 100644 --- a/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaMapper.kt +++ b/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaMapper.kt @@ -1,12 +1,12 @@ package icu.samnyan.aqua.sega.diva.util -import com.fasterxml.jackson.core.json.JsonWriteFeature -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.json.JsonMapper -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer +import tools.jackson.core.json.JsonWriteFeature +import tools.jackson.core.type.TypeReference +import tools.jackson.databind.DeserializationFeature +import tools.jackson.databind.ObjectMapper +import tools.jackson.databind.ext.javatime.deser.LocalDateTimeDeserializer +import tools.jackson.databind.json.JsonMapper +import tools.jackson.databind.module.SimpleModule import ext.JDict import icu.samnyan.aqua.sega.util.BooleanNumberDeserializer import icu.samnyan.aqua.sega.util.BooleanNumberSerializer @@ -18,19 +18,19 @@ import java.time.format.DateTimeFormatter @Component class DivaMapper { - private val mapper: ObjectMapper = JsonMapper.builder().enable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS) + private val mapper: ObjectMapper = JsonMapper.builder() + .addModule(SimpleModule().apply { + addSerializer(LocalDateTime::class.java, DivaDateTimeSerializer()) + addDeserializer(LocalDateTime::class.java, LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0"))) + addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer()) + addSerializer(Boolean::class.java, BooleanNumberSerializer()) + addSerializer(Boolean::class.javaPrimitiveType, BooleanNumberSerializer()) + addDeserializer(Boolean::class.java, BooleanNumberDeserializer()) + addDeserializer(Boolean::class.javaPrimitiveType, BooleanNumberDeserializer()) + }) + .enable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .build().apply { - registerModule(SimpleModule().apply { - addSerializer(LocalDateTime::class.java, DivaDateTimeSerializer()) - addDeserializer(LocalDateTime::class.java, LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0"))) - addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer()) - addSerializer(Boolean::class.java, BooleanNumberSerializer()) - addSerializer(Boolean::class.javaPrimitiveType, BooleanNumberSerializer()) - addDeserializer(Boolean::class.java, BooleanNumberDeserializer()) - addDeserializer(Boolean::class.javaPrimitiveType, BooleanNumberDeserializer()) - }) - } + .build() fun write(o: Any) = mapper.writeValueAsString(o) fun convert(map: JDict, toClass: Class) = mapper.convertValue(map, toClass) diff --git a/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaUtils.kt b/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaUtils.kt index e2413dbb..b4c3fc3d 100644 --- a/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaUtils.kt +++ b/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaUtils.kt @@ -1,8 +1,8 @@ package icu.samnyan.aqua.sega.diva.util -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.ser.std.StdSerializer +import tools.jackson.core.JsonGenerator +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.ser.std.StdSerializer import icu.samnyan.aqua.sega.diva.PlayerPvRecordRepository import icu.samnyan.aqua.sega.diva.model.common.Edition import icu.samnyan.aqua.sega.diva.model.common.LevelInfo @@ -30,7 +30,7 @@ object DivaTime { } class DivaDateTimeSerializer(t: Class? = null) : StdSerializer(t) { - override fun serialize(value: LocalDateTime, gen: JsonGenerator, provider: SerializerProvider) { + override fun serialize(value: LocalDateTime, gen: JsonGenerator, provider: SerializationContext) { gen.writeString(DivaTime.getString(value)) } } diff --git a/src/main/java/icu/samnyan/aqua/sega/general/BaseHandler.kt b/src/main/java/icu/samnyan/aqua/sega/general/BaseHandler.kt index 34bd81f0..1b00e8a3 100644 --- a/src/main/java/icu/samnyan/aqua/sega/general/BaseHandler.kt +++ b/src/main/java/icu/samnyan/aqua/sega/general/BaseHandler.kt @@ -1,13 +1,13 @@ package icu.samnyan.aqua.sega.general -import com.fasterxml.jackson.core.JsonProcessingException +import tools.jackson.core.JacksonException import ext.* import jakarta.servlet.http.HttpServletRequest import org.springframework.scheduling.annotation.Scheduled fun interface BaseHandler { - @Throws(JsonProcessingException::class) + @Throws(JacksonException::class) fun handle(request: Map): Any? } diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserAllHandler.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserAllHandler.kt index 5c5ad944..5fec7aa5 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserAllHandler.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserAllHandler.kt @@ -1,6 +1,6 @@ package icu.samnyan.aqua.sega.maimai2.handler -import com.fasterxml.jackson.core.JsonProcessingException +import tools.jackson.core.JacksonException import ext.mapApply import ext.minus import ext.unique @@ -28,7 +28,7 @@ class UpsertUserAllHandler( ) : BaseHandler { fun String.isValidUsername() = isNotBlank() && length <= 8 - @Throws(JsonProcessingException::class) + @Throws(JacksonException::class) override fun handle(request: Map): Any? { val upsertUserAll = mapper.convert(request, Mai2UpsertUserAll::class.java) val userId = upsertUserAll.userId diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/Mai2UserDetail.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/Mai2UserDetail.kt index 72e489b6..7cac8598 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/Mai2UserDetail.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/Mai2UserDetail.kt @@ -3,7 +3,7 @@ package icu.samnyan.aqua.sega.maimai2.model.userdata import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import icu.samnyan.aqua.net.games.BaseEntity import icu.samnyan.aqua.net.games.IUserData import icu.samnyan.aqua.sega.general.IntegerListConverter diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/UserEntities.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/UserEntities.kt index 1183c2cb..83bfe3bb 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/UserEntities.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/UserEntities.kt @@ -6,10 +6,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.databind.JsonSerializer -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.core.JsonGenerator +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.ValueSerializer +import tools.jackson.databind.annotation.JsonSerialize import ext.toJson import icu.samnyan.aqua.net.games.BaseEntity import icu.samnyan.aqua.net.games.IGenericGamePlaylog @@ -563,8 +563,8 @@ class UserRegions : Mai2UserEntity() { } val MAIMAI_DATETIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0") -class MaimaiDateSerializer : JsonSerializer() { - override fun serialize(v: LocalDateTime, j: JsonGenerator, s: SerializerProvider) { +class MaimaiDateSerializer : ValueSerializer() { + override fun serialize(v: LocalDateTime, j: JsonGenerator, s: SerializationContext) { j.writeString(v.format(MAIMAI_DATETIME)) } } diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/model/OngekiUserEntities.kt b/src/main/java/icu/samnyan/aqua/sega/ongeki/model/OngekiUserEntities.kt index d200a5f7..4e723e00 100644 --- a/src/main/java/icu/samnyan/aqua/sega/ongeki/model/OngekiUserEntities.kt +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/model/OngekiUserEntities.kt @@ -2,7 +2,7 @@ package icu.samnyan.aqua.sega.ongeki.model import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.sega.general.model.Card import icu.samnyan.aqua.sega.util.AccessCodeSerializer diff --git a/src/main/java/icu/samnyan/aqua/sega/util/BasicMapper.kt b/src/main/java/icu/samnyan/aqua/sega/util/BasicMapper.kt index a5b9eb01..eeeee8f4 100644 --- a/src/main/java/icu/samnyan/aqua/sega/util/BasicMapper.kt +++ b/src/main/java/icu/samnyan/aqua/sega/util/BasicMapper.kt @@ -1,17 +1,18 @@ package icu.samnyan.aqua.sega.util -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.json.JsonWriteFeature -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.SerializationFeature -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.databind.ser.std.StdSerializer -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import tools.jackson.core.JsonGenerator +import tools.jackson.core.json.JsonWriteFeature +import tools.jackson.core.type.TypeReference +import tools.jackson.databind.DeserializationFeature +import tools.jackson.databind.ObjectMapper +import tools.jackson.databind.cfg.EnumFeature +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.ext.javatime.deser.LocalDateTimeDeserializer +import tools.jackson.databind.ext.javatime.ser.LocalDateTimeSerializer +import tools.jackson.databind.json.JsonMapper +import tools.jackson.databind.module.SimpleModule +import tools.jackson.databind.ser.std.StdSerializer +import tools.jackson.module.kotlin.KotlinModule import ext.jsonArray import org.springframework.stereotype.Component import java.time.LocalDateTime @@ -28,11 +29,10 @@ open class IMapper(val mapper: ObjectMapper) { inline fun read(json: String): T = read(json, object : TypeReference() {}) } -val BASIC_MAPPER = jacksonObjectMapper().apply { - configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - configure(SerializationFeature.WRITE_ENUMS_USING_INDEX, true) - findAndRegisterModules() - registerModule(SimpleModule().apply { +val BASIC_MAPPER: ObjectMapper = JsonMapper.builder() + .addModule(KotlinModule.Builder().build()) + .findAndAddModules() + .addModule(SimpleModule().apply { addSerializer( LocalDateTime::class.java, LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0")) @@ -42,7 +42,9 @@ val BASIC_MAPPER = jacksonObjectMapper().apply { LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0")) ) }) -} + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(EnumFeature.WRITE_ENUMS_USING_INDEX, true) + .build() @Component class BasicMapper: IMapper(BASIC_MAPPER) @@ -50,17 +52,15 @@ class BasicMapper: IMapper(BASIC_MAPPER) val BOOLEAN_SERIALIZER = object : StdSerializer(Boolean::class.java) { - override fun serialize(v: Boolean, gen: JsonGenerator, p: SerializerProvider) { + override fun serialize(v: Boolean, gen: JsonGenerator, p: SerializationContext) { gen.writeString(v.toString()) } } -var STRING_MAPPER = jacksonObjectMapper().apply { - enable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS.mappedFeature()) - configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - configure(SerializationFeature.WRITE_ENUMS_USING_INDEX, true) - findAndRegisterModules() - registerModule(SimpleModule().apply { +var STRING_MAPPER: ObjectMapper = JsonMapper.builder() + .addModule(KotlinModule.Builder().build()) + .findAndAddModules() + .addModule(SimpleModule().apply { addSerializer( LocalDateTime::class.java, LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) @@ -72,7 +72,10 @@ var STRING_MAPPER = jacksonObjectMapper().apply { addSerializer(Boolean::class.javaObjectType, BOOLEAN_SERIALIZER) addSerializer(Boolean::class.javaPrimitiveType, BOOLEAN_SERIALIZER) }) -} + .enable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(EnumFeature.WRITE_ENUMS_USING_INDEX, true) + .build() @Component class StringMapper: IMapper(STRING_MAPPER) diff --git a/src/main/java/icu/samnyan/aqua/sega/util/GameDataService.kt b/src/main/java/icu/samnyan/aqua/sega/util/GameDataService.kt index 285866d9..ce830e97 100644 --- a/src/main/java/icu/samnyan/aqua/sega/util/GameDataService.kt +++ b/src/main/java/icu/samnyan/aqua/sega/util/GameDataService.kt @@ -1,6 +1,6 @@ package icu.samnyan.aqua.sega.util -import com.fasterxml.jackson.core.type.TypeReference +import tools.jackson.core.type.TypeReference import ext.logger import ext.toJson import icu.samnyan.aqua.sega.general.model.GameEncryptionKey diff --git a/src/main/java/icu/samnyan/aqua/sega/util/Serializers.kt b/src/main/java/icu/samnyan/aqua/sega/util/Serializers.kt index 05a2ff7a..7b84ed8b 100644 --- a/src/main/java/icu/samnyan/aqua/sega/util/Serializers.kt +++ b/src/main/java/icu/samnyan/aqua/sega/util/Serializers.kt @@ -1,24 +1,24 @@ package icu.samnyan.aqua.sega.util -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.core.JsonToken -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.ser.std.StdSerializer +import tools.jackson.core.JsonGenerator +import tools.jackson.core.JsonParser +import tools.jackson.core.JsonToken +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.ValueDeserializer +import tools.jackson.databind.ser.std.StdSerializer import ext.int import icu.samnyan.aqua.sega.general.model.Card import java.time.ZonedDateTime class AccessCodeSerializer @JvmOverloads constructor(t: Class? = null) : StdSerializer(t) { - override fun serialize(value: Card, gen: JsonGenerator, provider: SerializerProvider) { + override fun serialize(value: Card, gen: JsonGenerator, provider: SerializationContext) { gen.writeString(value.luid) } } -class BooleanToIntegerDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = when (p.currentToken) { +class BooleanToIntegerDeserializer : ValueDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = when (p.currentToken()) { JsonToken.VALUE_STRING -> when (val str = p.valueAsString.lowercase()) { "true" -> 1 "false" -> 0 @@ -32,16 +32,16 @@ class BooleanToIntegerDeserializer : JsonDeserializer() { } class BooleanNumberSerializer @JvmOverloads constructor(t: Class? = null) : StdSerializer(t) { - override fun serialize(value: Boolean, gen: JsonGenerator, provider: SerializerProvider) { + override fun serialize(value: Boolean, gen: JsonGenerator, provider: SerializationContext) { gen.writeNumber(if (value) 1 else 0) } } -class BooleanNumberDeserializer : JsonDeserializer() { +class BooleanNumberDeserializer : ValueDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = "0" != p.text } -class ZonedDateTimeDeserializer : JsonDeserializer() { +class ZonedDateTimeDeserializer : ValueDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = ZonedDateTime.parse(p.text) } diff --git a/src/test/kotlin/test/Chu3ImportJsonTest.kt b/src/test/kotlin/test/Chu3ImportJsonTest.kt index 34446f88..42f6f8e8 100644 --- a/src/test/kotlin/test/Chu3ImportJsonTest.kt +++ b/src/test/kotlin/test/Chu3ImportJsonTest.kt @@ -1,6 +1,6 @@ package test -import com.fasterxml.jackson.databind.JsonMappingException +import tools.jackson.databind.DatabindException import ext.JACKSON import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData import icu.samnyan.aqua.sega.chusan.model.userdata.UserCharge @@ -66,7 +66,7 @@ class Chu3ImportJsonTest : StringSpec({ } "invalid CHUNITHM dates are rejected with a useful message" { - val error = shouldThrow { + val error = shouldThrow { JACKSON.readValue( """{"firstPlayDate":"573-573-573T22:49:23"}""", Chu3UserData::class.java, @@ -74,6 +74,6 @@ class Chu3ImportJsonTest : StringSpec({ } error.originalMessage shouldBe - "Cannot deserialize value of type `java.time.LocalDateTime` from String \"573-573-573T22:49:23\": Invalid date-time; expected yyyy-MM-dd'T'HH:mm:ss or yyyy-MM-dd HH:mm:ss with an optional fractional second" + "Invalid date-time '573-573-573T22:49:23'; expected yyyy-MM-dd'T'HH:mm:ss or yyyy-MM-dd HH:mm:ss with an optional fractional second" } })