mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
Merge pull request #283 from square/jwilson.0420.more_kotlin_stuff
Handle nulls symetrically in KotlinJsonAdapter.
This commit is contained in:
@@ -72,13 +72,18 @@ internal class KotlinJsonAdapter<T> private constructor(
|
|||||||
}
|
}
|
||||||
reader.endObject()
|
reader.endObject()
|
||||||
|
|
||||||
// Call the constructor using a Map so that absent optionals get defaults.
|
// Confirm all parameters are present, optional, or nullable.
|
||||||
for (i in 0 until constructorSize) {
|
for (i in 0 until constructorSize) {
|
||||||
if (!constructor.parameters[i].isOptional && values[i] === ABSENT_VALUE) {
|
if (values[i] === ABSENT_VALUE && !constructor.parameters[i].isOptional) {
|
||||||
throw JsonDataException(
|
if (!constructor.parameters[i].type.isMarkedNullable) {
|
||||||
"Required value ${constructor.parameters[i].name} missing at ${reader.path}")
|
throw JsonDataException(
|
||||||
|
"Required value ${constructor.parameters[i].name} missing at ${reader.path}")
|
||||||
|
}
|
||||||
|
values[i] = null // Replace absent with null.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call the constructor using a Map so that absent optionals get defaults.
|
||||||
val result = constructor.callBy(IndexedParameterMap(constructor.parameters, values))
|
val result = constructor.callBy(IndexedParameterMap(constructor.parameters, values))
|
||||||
|
|
||||||
// Set remaining properties.
|
// Set remaining properties.
|
||||||
@@ -109,7 +114,7 @@ internal class KotlinJsonAdapter<T> private constructor(
|
|||||||
val parameter: KParameter?) {
|
val parameter: KParameter?) {
|
||||||
init {
|
init {
|
||||||
if (property !is KMutableProperty1 && parameter == null) {
|
if (property !is KMutableProperty1 && parameter == null) {
|
||||||
throw IllegalArgumentException("No constructor or var property for ${property.name}")
|
throw IllegalArgumentException("No constructor or var property for ${property}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,8 +193,7 @@ internal class KotlinJsonAdapter<T> private constructor(
|
|||||||
for (parameter in constructor.parameters) {
|
for (parameter in constructor.parameters) {
|
||||||
val binding = bindingsByName.remove(parameter.name)
|
val binding = bindingsByName.remove(parameter.name)
|
||||||
if (binding == null && !parameter.isOptional) {
|
if (binding == null && !parameter.isOptional) {
|
||||||
throw IllegalArgumentException(
|
throw IllegalArgumentException("No property for required constructor ${parameter}")
|
||||||
"No property for required constructor parameter ${parameter.name}")
|
|
||||||
}
|
}
|
||||||
bindings += binding
|
bindings += binding
|
||||||
}
|
}
|
||||||
|
@@ -162,7 +162,6 @@ class KotlinJsonAdapterTest {
|
|||||||
|
|
||||||
class ExplicitNull(var a: Int?, var b: Int?)
|
class ExplicitNull(var a: Int?, var b: Int?)
|
||||||
|
|
||||||
// TODO(jwilson): if a nullable field is absent, just do the obvious thing instead of crashing?
|
|
||||||
@Test fun absentNull() {
|
@Test fun absentNull() {
|
||||||
val moshi = Moshi.Builder().add(KotlinJsonAdapter.FACTORY).build()
|
val moshi = Moshi.Builder().add(KotlinJsonAdapter.FACTORY).build()
|
||||||
val jsonAdapter = moshi.adapter(AbsentNull::class.java)
|
val jsonAdapter = moshi.adapter(AbsentNull::class.java)
|
||||||
@@ -171,16 +170,27 @@ class KotlinJsonAdapterTest {
|
|||||||
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("{\"b\":5}")
|
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("{\"b\":5}")
|
||||||
assertThat(jsonAdapter.serializeNulls().toJson(encoded)).isEqualTo("{\"a\":null,\"b\":5}")
|
assertThat(jsonAdapter.serializeNulls().toJson(encoded)).isEqualTo("{\"a\":null,\"b\":5}")
|
||||||
|
|
||||||
try {
|
val decoded = jsonAdapter.fromJson("{\"b\":6}")
|
||||||
jsonAdapter.fromJson("{\"b\":6}")
|
assertThat(decoded.a).isNull()
|
||||||
fail()
|
assertThat(decoded.b).isEqualTo(6)
|
||||||
} catch(expected: JsonDataException) {
|
|
||||||
assertThat(expected).hasMessage("Required value a missing at $")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AbsentNull(var a: Int?, var b: Int?)
|
class AbsentNull(var a: Int?, var b: Int?)
|
||||||
|
|
||||||
|
@Test fun repeatedValue() {
|
||||||
|
val moshi = Moshi.Builder().add(KotlinJsonAdapter.FACTORY).build()
|
||||||
|
val jsonAdapter = moshi.adapter(RepeatedValue::class.java)
|
||||||
|
|
||||||
|
try {
|
||||||
|
jsonAdapter.fromJson("{\"a\":4,\"b\":null,\"b\":6}")
|
||||||
|
fail()
|
||||||
|
} catch(expected: JsonDataException) {
|
||||||
|
assertThat(expected).hasMessage("Multiple values for b at $.b")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RepeatedValue(var a: Int, var b: Int?)
|
||||||
|
|
||||||
@Test fun constructorParameterWithQualifier() {
|
@Test fun constructorParameterWithQualifier() {
|
||||||
val moshi = Moshi.Builder()
|
val moshi = Moshi.Builder()
|
||||||
.add(KotlinJsonAdapter.FACTORY)
|
.add(KotlinJsonAdapter.FACTORY)
|
||||||
@@ -384,6 +394,26 @@ class KotlinJsonAdapterTest {
|
|||||||
fun b() = b
|
fun b() = b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test fun privateConstructor() {
|
||||||
|
val moshi = Moshi.Builder().add(KotlinJsonAdapter.FACTORY).build()
|
||||||
|
val jsonAdapter = moshi.adapter(PrivateConstructor::class.java)
|
||||||
|
|
||||||
|
val encoded = PrivateConstructor.newInstance(3, 5)
|
||||||
|
assertThat(jsonAdapter.toJson(encoded)).isEqualTo("{\"a\":3,\"b\":5}")
|
||||||
|
|
||||||
|
val decoded = jsonAdapter.fromJson("{\"a\":4,\"b\":6}")
|
||||||
|
assertThat(decoded.a()).isEqualTo(4)
|
||||||
|
assertThat(decoded.b()).isEqualTo(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrivateConstructor private constructor(var a: Int, var b: Int) {
|
||||||
|
fun a() = a
|
||||||
|
fun b() = b
|
||||||
|
companion object {
|
||||||
|
fun newInstance(a: Int, b: Int) = PrivateConstructor(a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test fun privateProperties() {
|
@Test fun privateProperties() {
|
||||||
val moshi = Moshi.Builder().add(KotlinJsonAdapter.FACTORY).build()
|
val moshi = Moshi.Builder().add(KotlinJsonAdapter.FACTORY).build()
|
||||||
val jsonAdapter = moshi.adapter(PrivateProperties::class.java)
|
val jsonAdapter = moshi.adapter(PrivateProperties::class.java)
|
||||||
@@ -415,9 +445,37 @@ class KotlinJsonAdapterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test fun unsettableProperty() {
|
||||||
|
val moshi = Moshi.Builder().add(KotlinJsonAdapter.FACTORY).build()
|
||||||
|
try {
|
||||||
|
moshi.adapter(UnsettableProperty::class.java)
|
||||||
|
fail()
|
||||||
|
} catch(expected: IllegalArgumentException) {
|
||||||
|
assertThat(expected).hasMessage("No constructor or var property for " +
|
||||||
|
"val ${UnsettableProperty::class.qualifiedName}.a: kotlin.Int")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnsettableProperty {
|
||||||
|
val a: Int = -1
|
||||||
|
var b: Int = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun nonPropertyConstructorParameter() {
|
||||||
|
val moshi = Moshi.Builder().add(KotlinJsonAdapter.FACTORY).build()
|
||||||
|
try {
|
||||||
|
moshi.adapter(NonPropertyConstructorParameter::class.java)
|
||||||
|
fail()
|
||||||
|
} catch(expected: IllegalArgumentException) {
|
||||||
|
assertThat(expected).hasMessage(
|
||||||
|
"No property for required constructor parameter #0 a of " + "fun <init>(" +
|
||||||
|
"kotlin.Int, kotlin.Int): ${NonPropertyConstructorParameter::class.qualifiedName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonPropertyConstructorParameter(a: Int, val b: Int)
|
||||||
|
|
||||||
// TODO(jwilson): resolve generic types?
|
// TODO(jwilson): resolve generic types?
|
||||||
// TODO(jwilson): inaccessible constructors?
|
|
||||||
// TODO(jwilson): constructors parameter that is not a property
|
|
||||||
|
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
@JsonQualifier
|
@JsonQualifier
|
||||||
|
Reference in New Issue
Block a user