Spring Boot and Kotlin - How to validate a JSON object - spring

I'm using Spring Boot in Kotlin.
I'm taking in some JSON string, parsing it with ObjectMapper however I want to validate it has everything as in per the model - namely id and s3FilePath are not blank or missing.
So this is the model I want to validate against:
#JsonIgnoreProperties(ignoreUnknown = true)
class MyModel {
var id : String = ""
var s3FilePath : String = ""
}
This is where I use that model:
class FirstMessage {
fun create(newMessage: String) : String {
val objectMapper = ObjectMapper()
val parsedMap : MyModel = objectMapper.readValue(newMessage, MyModel::class.java)
val result = MyModel()
result.id = parsedMap.id
result.s3FilePath = parsedMap.s3FilePath
return objectMapper.writeValueAsString(result)
}
}
And finally I have this test where I want to validate an exception:
#Test
fun incompleteDataReturnsException() {
var input = """{"missing": "parts"}"""
// FirstMessage().create(input) // Will make some assertion here here
}
Any help would be appreciated. I've just started using Spring and its pretty 'intense'.
Thanks.
p.s. If creating that model wrong/there's a better way, please let me know. I'm a little unsure if thats the correct way.

You should use data classes for the models. Also, use kotlin jacksonObjectMapper() instead of ObjectMapper(). Standard ObjectMapper will not work in Kotlin. Or inject ObjectMapper from Spring context. Add "com.fasterxml.jackson.module:jackson-module-kotlin" in your dependencies.
#JsonIgnoreProperties(ignoreUnknown = true)
data class MyModel (
val id : String,
val s3FilePath : String
)
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
class FirstMessage {
fun create(newMessage: String) : String {
val parsedMap : MyModel = jacksonObjectMapper().readValue(newMessage)
return jacksonObjectMapper().writeValueAsString(parsedMap)
}
}
class FirstMessageTest {
#Test
fun incompleteDataReturnsException() {
val input = """{"missing": "parts"}"""
assertThrows (MissingKotlinParameterException::class.java
{FirstMessage().create(input)} // Will make some assertion here here
}
#Test
fun `Should parse`() {
val input = """{"id":"id",
"missing": "parts",
"s3FilePath":"somePath"}"""
FirstMessage().create(input) // Will make some assertion here here
}
}

If I understood your question correct, you just want to check if your required properties are set. So I would suggest checking for that properties after you parsed the string with something like this:
class FirstMessage {
fun create(newMessage: String) : String {
val objectMapper = ObjectMapper()
// validation 1: your input is valid JSON
val parsedMap : MyModel = objectMapper.readValue(newMessage, MyModel::class.java)
// validation 2: check that your properties are set
if(parsedMap.id.isNullOrEmpty() ||
parsedMap.s3FilePath.isNullOrEmpty())
{
throw IllegalArgumentException("Invalid input")
}
val result = MyModel()
result.id = parsedMap.id
result.s3FilePath = parsedMap.s3FilePath
return objectMapper.writeValueAsString(result)
}
}
Depending of the scope, a nicer solution would be a new annotation like #NotEmpty that you set on the properties of your target class that are required and have a generic parser function which validates all the annotated fields on your parsed object and throws a better exception which says exactly which fields are missing.

Related

spring json serialization issue

I am unable to get is_secure object attribute in json response, what is wrong with this code ?
#Configuration
class RouterConfiguration( ) {
#Bean
fun testRoutes(testHandler: TestHandler) = coRouter {
GET("/test", testHandler::testFunction)
}
}
data class TestClass(
val is_secure: Int? = 1,
val xyz: String?
)
#Component
class TestHandler{
suspend fun testFunction(request: ServerRequest): ServerResponse =
ServerResponse.ok().bodyValueAndAwait(TestClass(1,"abc"))
}
is prefixed fields (with camelCase or snake_case pattern) are only serialized if they are of type Boolean. You can find more details about it here.
If you wish to keep the is prefix, you may do so by using #get use-site target. Just use #get:JsonProperty("is_secure") on the is_secure field and it should do.

JSON key is missing (using #JsonComponent on Spring-boot with kotlin)

Thanks reading this question.
this problem confused me.
I created code that response JSON data like below.
#RestController
class JsonTestController {
#GetMapping("jsonTest")
fun jsonTest(): ResponseEntity<HaveBoolean> {
val value = BooleanValue(true)
return ResponseEntity.ok(HaveBoolean(value))
}
data class BooleanValue(val value: Boolean)
data class HaveBoolean(
val isAdmin: BooleanValue,
)
}
and #JsonComponent is below.
#JsonComponent
class BooleanValueJson {
class Serializer : JsonSerializer<JsonTestController.BooleanValue>() {
override fun serialize(value: JsonTestController.BooleanValue, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeBoolean(value.value)
}
}
class Deserializer : JsonDeserializer<JsonTestController.BooleanValue>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): JsonTestController.BooleanValue =
JsonTestController.BooleanValue(p.valueAsBoolean)
}
}
When I request localhost://8082/jsonTest, I got empty json ({}).
but, I tried other variable name like hoge, mean coding like below.
data class HaveBoolean(
val hoge: BooleanValue,
)
then, I request again, I can get correctly json ({"hoge": true}).
Can't I use isAdmin name on data class ?
Do you have any idea why this problem is happening?
thanks.
This is a known issue with jackson in kotlin. Jackson basically tries to remove is from the name but kotlin data class implementation doesn't have a proper getter without "is" resulting in mismatch. You can add JsonProperty("isAdmin") to the variable and it should work.
data class HaveBoolean(
#get:JsonProperty("isAdmin")
val isAdmin: BooleanValue,
)

Java jackson deserialize json - Getting symbol "=" instead of ":"

This is what I have. I'm trying to deserialize a json but it doesn't work:
public class MyClass {
private Object attribute;
#JsonCreator
public MyClass(Object attribute) {
this.attribute = attribute;
}
#JsonProperty("attr")
public Object getAttribute() {
return attribute;
}
}
public void method() {
InputStream eventsStream = this.getClass().getClassLoader()
.getResourceAsStream("fileName.json");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.readValue(eventsStream, MyClass.class);
}
fileName.json
{
"value1": 1,
"value2": [
{
"subValue1": "valueExample"
}
]
}
I'm getting this result with symbol "=" instead of ":":
{value1=1, value2=[{subValue=valueExample}]}
It must be a property or something like that. Any idea? Thanks
Why don't you save the deserialization into a variable?
MyClass myClassObject = objectMapper.readValue(eventsStream, MyClass.class);
if you do this, you will have direct the deserialized object in myClassObject and so you can use it easily.
Another method to deserialize would be to do it using Gson like:
new Gson().fromJson(eventStream.toString(), MyClass.class); //you need the eventStream as a String type
the import is this one:
import com.google.gson.Gson;
Can you please tell us why do you need to print the deserialized value?

Expression based Autowire in Spring Boot (with Kotlin)

Situation
I'm trying to come up with a methodology to conditionally load one bean (based on the existence of 2 property or environment variables) and if they are missing load up another bean.
Vars
So the two property (or env vars) are:
ProtocolHOST
ProtocolPORT
So for example java -jar xxxx -DProtocolHost=myMachine -DProtocolPort=3333 would use the bean I want, but if both are missing then you'd get another bean.
#Component("Protocol Enabled")
class YesBean : ProtocolService {}
#Component("Protocol Disabled")
class NoBean : ProtocolService {
Later in my controller I have a:
#Autowired
private lateinit var sdi : ProtocolService
So I've looked at a variety of options:
using both #ConditionalOnProperty and #ConditionalOnExpression and I cant seem to make any headway.
I'm pretty sure I need to go the Expression route so I wrote some test code that seems to be failing:
#PostConstruct
fun customInit() {
val sp = SpelExpressionParser()
val e1 = sp.parseExpression("'\${ProtocolHost}'")
println("${e1.valueType} ${e1.value}")
println(System.getProperty("ProtocolHost")
}
Which returns:
class java.lang.String ${ProtocolHost}
taco
So I'm not sure if my SPeL Parsing is working correctly because it looks like its just returning the string "${ProtocolHost}" instead of processing the correct value. I'm assuming this is why all the attempts I've made in the Expression Language are failing - and thus why i'm stuck.
Any assistance would be appreciated!
Thanks
Update
I did get things working by doing the following
in my main:
val protocolPort: String? = System.getProperty("ProtocolPort", System.getenv("ProtocolPort"))
val protocolHost: String? = System.getProperty("ProtocolHost", System.getenv("ProtocolHost"))
System.setProperty("use.protocol", (protocolHost != null && protocolPort != null).toString())
runApplication<SddfBridgeApplication>(*args)
And then on the bean definitions:
#ConditionalOnProperty(prefix = "use", name = arrayOf("protocol"), havingValue = "false", matchIfMissing = false)
#ConditionalOnProperty(prefix = "use", name = arrayOf("protocol"), havingValue = "false", matchIfMissing = false)
However this feels like a hack and I'm hoping it could be done directly in SpEL instead of pre-settings vars a head of time.
This sounds like a perfect use case for Java based bean configuration:
#Configuration
class DemoConfiguration {
#Bean
fun createProtocolService(): ProtocolService {
val protocolPort: String? = System.getProperty("ProtocolPort", System.getenv("ProtocolPort"))
val protocolHost: String? = System.getProperty("ProtocolHost", System.getenv("ProtocolHost"))
return if(!protocolHost.isNullOrEmpty() && !protocolPort.isNullOrEmpty()) {
YesBean()
} else {
NoBean()
}
}
}
open class ProtocolService
class YesBean : ProtocolService()
class NoBean : ProtocolService()
You might also want look into Externalized Configurations to replace System.getProperty() and System.getenv().
This would then look like this:
#Configuration
class DemoConfiguration {
#Bean
fun createProtocolService(#Value("\${protocol.port:0}") protocolPort: Int,
#Value("\${protocol.host:none}") protocolHost: String): ProtocolService {
return if (protocolHost != "none" && protocolPort != 0) {
YesBean()
} else {
NoBean()
}
}
}

Using Scala classes as DTOs in Spring MVC

In my project I'm using Spring + Scala.
Some of my Spring MVC controllers uses Spring feature for binding incoming HTTP parameters to DTO object. Like this:
#RequestMapping(value = Array("/", ""), method = Array(RequestMethod.POST))
def saveProduct(dto: MyDto): Iterable[MyDto] = {...}
And MyDto is simple scala class:
class MyDto extends Serializable {
#BeanProperty var id : Long = _
#BeanProperty var name: String = _
}
My problem is that I'm getting exceptions when trying to use Scala Option class for fields in MyDto:
class MyDto extends Serializable {
#BeanProperty var id : Option[Long] = None
#BeanProperty var name: Option[String] = None
}
Exception message is:
Failed to convert property value of type 'java.lang.String' to required type 'scala.Option' for property 'name';
What I can do to use Scala Options as type if fields in MyDto?
I am not a Scala expert, but here is one way:
Create a converter, along these lines:
import org.springframework.core.convert.converter.Converter
class TypeToOptionOfTypeConverter[T] extends Converter[T, Option[T]] {
override def convert(source: T): Option[T] = {
Some(source)
}
}
Register this converter with Spring MVC:
class WebConfig extends WebMvcConfigurerAdapter {
override def addFormatters(registry: FormatterRegistry): Unit = {
registry.addConverter(classOf[String], classOf[Option[String]], new TypeToOptionOfTypeConverter[String])
registry.addConverter(classOf[Long], classOf[Option[Long]], new TypeToOptionOfTypeConverter[Long])
}
}
That should be it, now your DTO should get cleanly mapped.
Spring has support for converting types using converters with its data binding. You will need to implement the converter so that Spring knows how to convert, for example, String to Option[String].
See:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert

Resources