Using Micronaut Serde, any string value other than "true"
returns false
, and any numeric value other than 0 returns true
.
Is this desirable behavior or should I open an issue?
For example, reading string values ""
, "True"
, "TRUE"
or "enabled"
from a JSON returns false
, which can generate erroneous entries in an API.
Actual Behaviour
Comparing with Javascript behavior, if you run JSON.parse("True")
or JSON.parse("TRUE")
in the browser console, an exception will be thrown: SyntaxError: Unexpected token 'T', "True" is not valid JSON
. According to the JSON specification, the expected values are true
or false
, without quotes. If we wanted to strictly follow the specification, no string values would be allowed.
import io.micronaut.serde.ObjectMapper
import io.micronaut.serde.annotation.Serdeable
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.inject.Inject
import spock.lang.Specification
@MicronautTest
class CurrentBehaviorOfBooleanSerdeSpec extends Specification {
@Inject ObjectMapper objectMapper
// All asserts in this method are passing
void "test current behavior"() {
expect:
objectMapper.readValue('{"enabled": true}', Test).enabled == true
objectMapper.readValue('{"enabled": "true"}', Test).enabled == true
objectMapper.readValue('{"enabled": false}', Test).enabled == false
objectMapper.readValue('{"enabled": "false"}', Test).enabled == false
// If there is at least one uppercase letter, returns false, even if the value is true
// It is acceptable because true or false as a string is not in the Json specification
objectMapper.readValue('{"enabled": "True"}', Test).enabled == false
objectMapper.readValue('{"enabled": "TRUE"}', Test).enabled == false
// Any numeric value other than 0 returns true
objectMapper.readValue('{"enabled": 0}', Test).enabled == false
objectMapper.readValue('{"enabled": 1}', Test).enabled == true
objectMapper.readValue('{"enabled": 99}', Test).enabled == true
// Any string value other than true returns false
objectMapper.readValue('{"enabled": ""}', Test).enabled == false
objectMapper.readValue('{"enabled": " "}', Test).enabled == false
objectMapper.readValue('{"enabled": "enabled"}', Test).enabled == false
}
@Serdeable
static class Test {
Boolean enabled
}
}
Expected Behaviour
All commands in "test expected behavior with invalid values"
should throw an exception instead of returning true
or false
.
import io.micronaut.serde.ObjectMapper
import io.micronaut.serde.annotation.Serdeable
import io.micronaut.serde.exceptions.InvalidPropertyFormatException
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.inject.Inject
import spock.lang.Specification
@MicronautTest
class ExpectedBehaviorOfBooleanSerdeSpec extends Specification {
@Inject ObjectMapper objectMapper
// All asserts in this method are passing
void "test expected behavior with valid values"() {
expect:
objectMapper.readValue('{"enabled": true}', Test).enabled == true
objectMapper.readValue('{"enabled": "true"}', Test).enabled == true
objectMapper.readValue('{"enabled": false}', Test).enabled == false
objectMapper.readValue('{"enabled": "false"}', Test).enabled == false
// Could only accept numeric values 0 and 1
objectMapper.readValue('{"enabled": 0}', Test).enabled == false
objectMapper.readValue('{"enabled": 1}', Test).enabled == true
}
// All asserts in this method are failing
void "test expected behavior with invalid values"() {
when:
// Any string value other than true or false could throw an exception, instead of returning false
objectMapper.readValue('{"enabled": "True"}', Test).enabled
objectMapper.readValue('{"enabled": "TRUE"}', Test).enabled
objectMapper.readValue('{"enabled": "False"}', Test).enabled
objectMapper.readValue('{"enabled": "FALSE"}', Test).enabled
objectMapper.readValue('{"enabled": ""}', Test).enabled
objectMapper.readValue('{"enabled": " "}', Test).enabled
objectMapper.readValue('{"enabled": "enabled"}', Test).enabled
// Any numeric value other than 0 and 1 could throw an exception, instead of returning true
objectMapper.readValue('{"enabled": 99}', Test).enabled
then:
thrown(InvalidPropertyFormatException)
}
@Serdeable
static class Test {
Boolean enabled
}
}