Skip to content

Commit fd843b4

Browse files
committed
Merge branch '2.13'
2 parents 214af87 + 3933aeb commit fd843b4

File tree

9 files changed

+265
-26
lines changed

9 files changed

+265
-26
lines changed

release-notes/CREDITS-2.x

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ Authors:
1313

1414
Contributors:
1515

16+
Eric Fenderbosch (efenderbosch@github)
17+
* Fixed #182: Serialize unsigned numbers
18+
(2.12.2)
19+
1620
Elisha Peterson (triathematician@github)
1721
* Reported #409: `module-info.java` missing "exports"
1822
(2.12.2)

release-notes/VERSION-2.x

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ No changes since 2.12
2222

2323
#409: `module-info.java` missing "exports"
2424
(reported by Elisha P)
25+
#182: Nullable unsigned numbers do not serialize correctly
26+
(reported by bholzman@github)
27+
(fix contributed by Eric F)
2528

2629
2.12.1 (08-Jan-2021)
2730

src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ fun jacksonMapperBuilder(): JsonMapper.Builder = JsonMapper.builder().addModule(
3434

3535
inline fun <reified T> jacksonTypeRef(): TypeReference<T> = object : TypeReference<T>() {}
3636

37-
inline fun <reified T> ObjectMapper.readValue(jp: JsonParser): T = readValue(jp, jacksonTypeRef<T>())
38-
inline fun <reified T> ObjectMapper.readValues(jp: JsonParser): MappingIterator<T> = readValues(jp, jacksonTypeRef<T>())
37+
inline fun <reified T> ObjectMapper.readValue(p: JsonParser): T = readValue(p, jacksonTypeRef<T>())
38+
inline fun <reified T> ObjectMapper.readValues(p: JsonParser): MappingIterator<T> = readValues(p, jacksonTypeRef<T>())
3939

4040
inline fun <reified T> ObjectMapper.readValue(src: File): T = readValue(src, jacksonTypeRef<T>())
4141
inline fun <reified T> ObjectMapper.readValue(src: URL): T = readValue(src, jacksonTypeRef<T>())
@@ -47,8 +47,7 @@ inline fun <reified T> ObjectMapper.readValue(src: ByteArray): T = readValue(src
4747
inline fun <reified T> ObjectMapper.treeToValue(n: TreeNode): T? = treeToValue(n, T::class.java)
4848
inline fun <reified T> ObjectMapper.convertValue(from: Any): T = convertValue(from, jacksonTypeRef<T>())
4949

50-
inline fun <reified T> ObjectReader.readValueTyped(jp: JsonParser): T = readValue(jp, jacksonTypeRef<T>())
51-
inline fun <reified T> ObjectReader.readValuesTyped(jp: JsonParser): Iterator<T> = readValues(jp, jacksonTypeRef<T>())
50+
inline fun <reified T> ObjectReader.readValuesTyped(p: JsonParser): Iterator<T> = readValues(p, jacksonTypeRef<T>())
5251
inline fun <reified T> ObjectReader.treeToValue(n: TreeNode): T? = treeToValue(n, T::class.java)
5352

5453
internal fun DatabindException.wrapWithPath(refFrom: Any?, refFieldName: String) = DatabindException.wrapWithPath(this, refFrom, refFieldName)

src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinDeserializers.kt

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
@file:Suppress("EXPERIMENTAL_API_USAGE")
2+
13
package com.fasterxml.jackson.module.kotlin
24

35
import com.fasterxml.jackson.core.JsonParser
6+
import com.fasterxml.jackson.core.JsonToken.VALUE_NUMBER_INT
7+
import com.fasterxml.jackson.core.exc.InputCoercionException
48
import com.fasterxml.jackson.databind.*
59
import com.fasterxml.jackson.databind.deser.Deserializers
610
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
@@ -12,7 +16,7 @@ object SequenceDeserializer : StdDeserializer<Sequence<*>>(Sequence::class.java)
1216
}
1317
}
1418

15-
object RegexDeserializer: StdDeserializer<Regex>(Regex::class.java) {
19+
object RegexDeserializer : StdDeserializer<Regex>(Regex::class.java) {
1620
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Regex {
1721
val node = ctxt.readTree(p)
1822

@@ -36,6 +40,7 @@ object RegexDeserializer: StdDeserializer<Regex>(Regex::class.java) {
3640
}
3741
}
3842

43+
/*
3944
internal class KotlinDeserializers: Deserializers.Base() {
4045
override fun findBeanDeserializer(type: JavaType, config: DeserializationConfig?, beanDesc: BeanDescription?): ValueDeserializer<*>? {
4146
return if (type.isInterface && type.rawClass == Sequence::class.java) {
@@ -44,12 +49,73 @@ internal class KotlinDeserializers: Deserializers.Base() {
4449
RegexDeserializer
4550
} else {
4651
null
52+
*/
53+
object UByteDeserializer : StdDeserializer<UByte>(UByte::class.java) {
54+
override fun deserialize(p: JsonParser, ctxt: DeserializationContext) =
55+
p.shortValue.asUByte() ?: throw InputCoercionException(
56+
p,
57+
"Numeric value (${p.text}) out of range of UByte (0 - ${UByte.MAX_VALUE}).",
58+
VALUE_NUMBER_INT,
59+
UByte::class.java
60+
)
61+
}
62+
63+
object UShortDeserializer : StdDeserializer<UShort>(UShort::class.java) {
64+
override fun deserialize(p: JsonParser, ctxt: DeserializationContext) =
65+
p.intValue.asUShort() ?: throw InputCoercionException(
66+
p,
67+
"Numeric value (${p.text}) out of range of UShort (0 - ${UShort.MAX_VALUE}).",
68+
VALUE_NUMBER_INT,
69+
UShort::class.java
70+
)
71+
}
72+
73+
object UIntDeserializer : StdDeserializer<UInt>(UInt::class.java) {
74+
override fun deserialize(p: JsonParser, ctxt: DeserializationContext) =
75+
p.longValue.asUInt() ?: throw InputCoercionException(
76+
p,
77+
"Numeric value (${p.text}) out of range of UInt (0 - ${UInt.MAX_VALUE}).",
78+
VALUE_NUMBER_INT,
79+
UInt::class.java
80+
)
81+
}
82+
83+
object ULongDeserializer : StdDeserializer<ULong>(ULong::class.java) {
84+
override fun deserialize(p: JsonParser, ctxt: DeserializationContext) =
85+
p.bigIntegerValue.asULong() ?: throw InputCoercionException(
86+
p,
87+
"Numeric value (${p.text}) out of range of ULong (0 - ${ULong.MAX_VALUE}).",
88+
VALUE_NUMBER_INT,
89+
ULong::class.java
90+
)
91+
}
92+
93+
@ExperimentalUnsignedTypes
94+
internal class KotlinDeserializers : Deserializers.Base() {
95+
override fun findBeanDeserializer(
96+
type: JavaType,
97+
config: DeserializationConfig?,
98+
beanDesc: BeanDescription?
99+
): ValueDeserializer<*>? {
100+
return when {
101+
type.isInterface && type.rawClass == Sequence::class.java -> SequenceDeserializer
102+
type.rawClass == Regex::class.java -> RegexDeserializer
103+
type.rawClass == UByte::class.java -> UByteDeserializer
104+
type.rawClass == UShort::class.java -> UShortDeserializer
105+
type.rawClass == UInt::class.java -> UIntDeserializer
106+
type.rawClass == ULong::class.java -> ULongDeserializer
107+
else -> null
47108
}
48109
}
49110

50111
override fun hasDeserializerFor(config: DeserializationConfig,
51112
valueType: Class<*>): Boolean {
52113
return valueType == Sequence::class.java
53-
|| valueType == Regex::class.java;
114+
|| valueType == Regex::class.java
115+
|| valueType == UByte::class.java
116+
|| valueType == UShort::class.java
117+
|| valueType == UInt::class.java
118+
|| valueType == ULong::class.java
119+
;
54120
}
55121
}
Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
@file:Suppress("EXPERIMENTAL_API_USAGE")
2+
13
package com.fasterxml.jackson.module.kotlin
24

35
import com.fasterxml.jackson.annotation.JsonFormat
46
import com.fasterxml.jackson.core.JsonGenerator
57
import com.fasterxml.jackson.databind.*
68
import com.fasterxml.jackson.databind.ser.Serializers
79
import com.fasterxml.jackson.databind.ser.std.StdSerializer
10+
import java.math.BigInteger
811

912
object SequenceSerializer : StdSerializer<Sequence<*>>(Sequence::class.java) {
1013
override fun serialize(value: Sequence<*>, gen: JsonGenerator, provider: SerializerProvider) {
@@ -13,15 +16,44 @@ object SequenceSerializer : StdSerializer<Sequence<*>>(Sequence::class.java) {
1316
}
1417
}
1518

16-
internal class KotlinSerializers : Serializers.Base() {
17-
override fun findSerializer(config: SerializationConfig,
18-
type: JavaType,
19-
beanDesc: BeanDescription?,
20-
formatOverrides: JsonFormat.Value?): ValueSerializer<*>? {
21-
return if (Sequence::class.java.isAssignableFrom(type.rawClass)) {
22-
SequenceSerializer
23-
} else {
24-
null
19+
object UByteSerializer : StdSerializer<UByte>(UByte::class.java) {
20+
override fun serialize(value: UByte, gen: JsonGenerator, provider: SerializerProvider) =
21+
gen.writeNumber(value.toShort())
22+
}
23+
24+
object UShortSerializer : StdSerializer<UShort>(UShort::class.java) {
25+
override fun serialize(value: UShort, gen: JsonGenerator, provider: SerializerProvider) =
26+
gen.writeNumber(value.toInt())
27+
}
28+
29+
object UIntSerializer : StdSerializer<UInt>(UInt::class.java) {
30+
override fun serialize(value: UInt, gen: JsonGenerator, provider: SerializerProvider) =
31+
gen.writeNumber(value.toLong())
32+
}
33+
34+
object ULongSerializer : StdSerializer<ULong>(ULong::class.java) {
35+
override fun serialize(value: ULong, gen: JsonGenerator, provider: SerializerProvider) {
36+
val longValue = value.toLong()
37+
when {
38+
longValue >= 0 -> gen.writeNumber(longValue)
39+
else -> gen.writeNumber(BigInteger(value.toString()))
2540
}
2641
}
42+
}
43+
44+
@Suppress("EXPERIMENTAL_API_USAGE")
45+
internal class KotlinSerializers : Serializers.Base() {
46+
override fun findSerializer(
47+
config: SerializationConfig?,
48+
type: JavaType,
49+
beanDesc: BeanDescription?,
50+
formatOverrides: JsonFormat.Value?
51+
): ValueSerializer<*>? = when {
52+
Sequence::class.java.isAssignableFrom(type.rawClass) -> SequenceSerializer
53+
UByte::class.java.isAssignableFrom(type.rawClass) -> UByteSerializer
54+
UShort::class.java.isAssignableFrom(type.rawClass) -> UShortSerializer
55+
UInt::class.java.isAssignableFrom(type.rawClass) -> UIntSerializer
56+
ULong::class.java.isAssignableFrom(type.rawClass) -> ULongSerializer
57+
else -> null
58+
}
2759
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
@file:Suppress("EXPERIMENTAL_API_USAGE")
2+
3+
package com.fasterxml.jackson.module.kotlin
4+
5+
import java.math.BigInteger
6+
7+
fun Short.asUByte() = when {
8+
this >= 0 && this <= UByte.MAX_VALUE.toShort() -> this.toUByte()
9+
else -> null
10+
}
11+
12+
fun Int.asUShort() = when {
13+
this >= 0 && this <= UShort.MAX_VALUE.toInt() -> this.toUShort()
14+
else -> null
15+
}
16+
17+
fun Long.asUInt() = when {
18+
this >= 0 && this <= UInt.MAX_VALUE.toLong() -> this.toUInt()
19+
else -> null
20+
}
21+
22+
private val uLongMaxValue = BigInteger(ULong.MAX_VALUE.toString())
23+
fun BigInteger.asULong() = when {
24+
this >= BigInteger.ZERO && this <= uLongMaxValue -> this.toLong().toULong()
25+
else -> null
26+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
2+
3+
package com.fasterxml.jackson.module.kotlin.test
4+
5+
import com.fasterxml.jackson.core.exc.InputCoercionException
6+
import com.fasterxml.jackson.databind.ObjectMapper
7+
import com.fasterxml.jackson.databind.SerializationFeature
8+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
9+
import com.fasterxml.jackson.module.kotlin.readValue
10+
import org.junit.Test
11+
import java.math.BigInteger
12+
import org.hamcrest.CoreMatchers.equalTo
13+
import org.hamcrest.MatcherAssert.assertThat
14+
import org.junit.Assert.assertThrows
15+
16+
internal class UnsignedNumbersTests {
17+
18+
val mapper: ObjectMapper = jacksonObjectMapper()
19+
20+
@Test
21+
fun `test UByte`() {
22+
val json = mapper.writeValueAsString(UByte.MAX_VALUE)
23+
val deserialized = mapper.readValue<UByte>(json)
24+
assertThat(deserialized, equalTo(UByte.MAX_VALUE))
25+
}
26+
27+
@Test
28+
fun `test UByte overflow`() {
29+
val json = mapper.writeValueAsString(UByte.MAX_VALUE + 1u)
30+
assertThrows(InputCoercionException::class.java) { mapper.readValue<UByte>(json) }
31+
}
32+
33+
@Test
34+
fun `test UByte underflow`() {
35+
val json = mapper.writeValueAsString(-1)
36+
assertThrows(InputCoercionException::class.java) { mapper.readValue<UByte>(json) }
37+
}
38+
39+
@Test
40+
fun `test UShort`() {
41+
val json = mapper.writeValueAsString(UShort.MAX_VALUE)
42+
val deserialized = mapper.readValue<UShort>(json)
43+
assertThat(deserialized, equalTo(UShort.MAX_VALUE))
44+
}
45+
46+
@Test
47+
fun `test UShort overflow`() {
48+
val json = mapper.writeValueAsString(UShort.MAX_VALUE + 1u)
49+
assertThrows(InputCoercionException::class.java) { mapper.readValue<UShort>(json) }
50+
}
51+
52+
@Test
53+
fun `test UShort underflow`() {
54+
val json = mapper.writeValueAsString(-1)
55+
assertThrows(InputCoercionException::class.java) { mapper.readValue<UShort>(json) }
56+
}
57+
58+
@Test
59+
fun `test UInt`() {
60+
val json = mapper.writeValueAsString(UInt.MAX_VALUE)
61+
val deserialized = mapper.readValue<UInt>(json)
62+
assertThat(deserialized, equalTo(UInt.MAX_VALUE))
63+
}
64+
65+
@Test
66+
fun `test UInt overflow`() {
67+
val json = mapper.writeValueAsString(UInt.MAX_VALUE.toULong() + 1u)
68+
assertThrows(InputCoercionException::class.java) { mapper.readValue<UInt>(json) }
69+
}
70+
71+
@Test
72+
fun `test UInt underflow`() {
73+
val json = mapper.writeValueAsString(-1)
74+
assertThrows(InputCoercionException::class.java) { mapper.readValue<UInt>(json) }
75+
}
76+
77+
@Test
78+
fun `test ULong`() {
79+
val json = mapper.writeValueAsString(ULong.MAX_VALUE)
80+
val deserialized = mapper.readValue<ULong>(json)
81+
assertThat(deserialized, equalTo(ULong.MAX_VALUE))
82+
}
83+
84+
@Test
85+
fun `test ULong overflow`() {
86+
val value = BigInteger(ULong.MAX_VALUE.toString()) + BigInteger.ONE
87+
val json = mapper.writeValueAsString(value)
88+
assertThrows(InputCoercionException::class.java) { mapper.readValue<ULong>(json) }
89+
}
90+
91+
@Test
92+
fun `test ULong underflow`() {
93+
val json = mapper.writeValueAsString(-1)
94+
assertThrows(InputCoercionException::class.java) { mapper.readValue<ULong>(json) }
95+
}
96+
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.fasterxml.jackson.module.kotlin.test.github.failing
1+
package com.fasterxml.jackson.module.kotlin.test.github
22

33
import com.fasterxml.jackson.annotation.JsonSubTypes
44
import com.fasterxml.jackson.annotation.JsonSubTypes.Type
@@ -7,9 +7,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo.As
77
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id
88
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
99
import com.fasterxml.jackson.module.kotlin.readValue
10-
import com.fasterxml.jackson.module.kotlin.test.expectFailure
1110
import org.junit.Test
12-
import kotlin.test.assertNull
11+
import kotlin.test.assertEquals
1312

1413
class Github335Test {
1514
val mapper = jacksonObjectMapper()
@@ -18,10 +17,10 @@ class Github335Test {
1817
data class UniquePayload(val data: String) : Payload
1918

2019
data class MyEntity(
21-
val type: String?,
22-
@JsonTypeInfo(use = Id.NAME, include = As.EXTERNAL_PROPERTY, property = "type")
23-
@JsonSubTypes(Type(value = UniquePayload::class, name = "UniquePayload"))
24-
val payload: Payload?
20+
val type: String?,
21+
@JsonTypeInfo(use = Id.NAME, include = As.EXTERNAL_PROPERTY, property = "type")
22+
@JsonSubTypes(Type(value = UniquePayload::class, name = "UniquePayload"))
23+
val payload: Payload?
2524
)
2625

2726
@Test
@@ -30,9 +29,6 @@ class Github335Test {
3029
val json = mapper.writeValueAsString(oldEntity)
3130
val newEntity = mapper.readValue<MyEntity>(json)
3231

33-
expectFailure<AssertionError>("GitHub #335 has been fixed!") {
34-
// newEntity.type is the string "null" instead of the null value
35-
assertNull(newEntity.type)
36-
}
32+
assertEquals(oldEntity, newEntity)
3733
}
3834
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.fasterxml.jackson.module.kotlin.test.github.failing
2+
3+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
4+
import com.fasterxml.jackson.module.kotlin.readValue
5+
import com.fasterxml.jackson.module.kotlin.test.expectFailure
6+
import org.junit.Test
7+
import kotlin.test.assertSame
8+
9+
class TestGithub196 {
10+
@Test
11+
fun testUnitSingletonDeserialization() {
12+
// An empty object should be deserialized as *the* Unit instance, but is not
13+
expectFailure<AssertionError>("GitHub #196 has been fixed!") {
14+
assertSame(jacksonObjectMapper().readValue("{}"), Unit)
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)