[F] Fix Jackson 3 export isSomething serialization bug

This commit is contained in:
Azalea
2026-08-05 18:39:50 +00:00
parent cb13949f51
commit a2c257091c
18 changed files with 148 additions and 136 deletions

View File

@@ -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")

View File

@@ -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<Boolean>() {
val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, object : ValueDeserializer<Boolean>() {
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<LocalDateTime>() {
.addDeserializer(LocalDateTime::class.java, object : ValueDeserializer<LocalDateTime>() {
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<Int>()
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 <reified T> ObjectMapper.parse(str: Str) = readValue(str, T::class.java)
inline fun <reified T> 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
}

View File

@@ -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<ExportModel: IExportClass<UserModel>, UserModel:
json.parseJackson(exportClass.java)
} catch (e: Exception) {
val jsonError = generateSequence<Throwable>(e) { it.cause }
.filterIsInstance<JsonProcessingException>()
.filterIsInstance<JacksonException>()
.firstOrNull()
?: throw e

View File

@@ -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<List<Integer>>() {
val JSON_INT_LIST_STR = SimpleModule().addDeserializer(List::class.java, object : ValueDeserializer<List<Integer>>() {
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)
}
val JACKSON_ARTEMIS = JACKSON.rebuild().addModule(JSON_INT_LIST_STR).build()

View File

@@ -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

View File

@@ -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<LocalDateTime?>() {
class FlexibleDateTimeDeserializer : ValueDeserializer<LocalDateTime?>() {
@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<LocalDateTime?>() {
val value = p.text
return FORMATTERS.firstNotNullOfOrNull { formatter ->
runCatching { LocalDateTime.parse(value, formatter) }.getOrNull()
} ?: throw ctxt.weirdStringException(
value,
} ?: ctxt.reportInputMismatch<LocalDateTime>(
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,
)
}

View File

@@ -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

View File

@@ -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 <T> convert(map: JDict, toClass: Class<T>) = mapper.convertValue<T>(map, toClass)

View File

@@ -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<LocalDateTime>? = null) : StdSerializer<LocalDateTime>(t) {
override fun serialize(value: LocalDateTime, gen: JsonGenerator, provider: SerializerProvider) {
override fun serialize(value: LocalDateTime, gen: JsonGenerator, provider: SerializationContext) {
gen.writeString(DivaTime.getString(value))
}
}

View File

@@ -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<String, Any>): Any?
}

View File

@@ -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<String, Any>): Any? {
val upsertUserAll = mapper.convert(request, Mai2UpsertUserAll::class.java)
val userId = upsertUserAll.userId

View File

@@ -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

View File

@@ -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<LocalDateTime>() {
override fun serialize(v: LocalDateTime, j: JsonGenerator, s: SerializerProvider) {
class MaimaiDateSerializer : ValueSerializer<LocalDateTime>() {
override fun serialize(v: LocalDateTime, j: JsonGenerator, s: SerializationContext) {
j.writeString(v.format(MAIMAI_DATETIME))
}
}

View File

@@ -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

View File

@@ -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 <reified T> read(json: String): T = read(json, object : TypeReference<T>() {})
}
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>(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)

View File

@@ -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

View File

@@ -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<Card>? = null) : StdSerializer<Card>(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<Int>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = when (p.currentToken) {
class BooleanToIntegerDeserializer : ValueDeserializer<Int>() {
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<Int>() {
}
class BooleanNumberSerializer @JvmOverloads constructor(t: Class<Boolean>? = null) : StdSerializer<Boolean>(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<Boolean>() {
class BooleanNumberDeserializer : ValueDeserializer<Boolean>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = "0" != p.text
}
class ZonedDateTimeDeserializer : JsonDeserializer<ZonedDateTime>() {
class ZonedDateTimeDeserializer : ValueDeserializer<ZonedDateTime>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = ZonedDateTime.parse(p.text)
}

View File

@@ -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<JsonMappingException> {
val error = shouldThrow<DatabindException> {
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"
}
})