passing json in json to spring controller - ajax

I am trying to pass json object to spring controller and I manage to do that, but value of one property is in json and I think that I have problem because of it. But there is no other way to pass that data. Code is below,
data class:
#Entity
data class Section(
#Id
#GeneratedValue
val id: Long = 0L,
val name: String = "",
var text: String,
#ManyToOne
var notebook: Notebook
)
Controller code:
#PutMapping("/sections/{id}")
fun updateSection(#RequestBody section: Section, #PathVariable id: Long): Section =
sectionRepository.findById(id).map {
it.text = section.text
it.notebook = section.notebook
sectionRepository.save(it)
}.orElseThrow { SectionNotFoundException(id) }
javascript sending post to api:
function updateApi(data) {
axios.put(MAIN_URL + 'sections/' + data.id, {
data
})
.then(showChangesSaved())
.catch(ShowErrorSync());
}
function saveSection() {
var data = JSON.parse(window.sessionStorage.getItem("curr-section"));
data.text = JSON.stringify(element.editor).toString();
updateApi(data);
}
I get error like this:
2020-11-18 15:06:24.052 WARN 16172 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Instantiation of [simple type, class org.dn.model.Section] value failed for JSON property text due to missing (therefore NULL) value for creator parameter text which is a non-nullable type; nested exception is com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class org.dn.model.Section] value failed for JSON property text due to missing (therefore NULL) value for creator parameter text which is a non-nullable type
at [Source: (PushbackInputStream); line: 1, column: 375] (through reference chain: org.dn.model.Section["text"])]
so text in element.editor is JSON formatted string and I need to pass it as it is to controller. Is there any way to do that? I tried searching, but I can't find json in json help...
Whole project is available on github

What does your json looks like? If I check out your project and run the following two tests:
one with Section as an object as request body
one with Section as json
Both will succeed. So the problem might lie in your JSON:
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class HttpRequestTest {
#LocalServerPort
private val port = 0
#Autowired
private val restTemplate: TestRestTemplate? = null
#Test
fun sectionAsObject() {
val section = Section(0L, "2L", "text", Notebook(1L, "1", "2"))
assertThat(restTemplate!!.put("http://localhost:$port/sections/123", section
)).isNotNull
}
#Test
fun sectionAsJson() {
val sectionAsJson = """
{
"id": 0,
"name": "aName",
"text": "aText",
"noteBook": {
"id": 0,
"name": "aName",
"desc": "2"
}
}
""".trimIndent()
assertThat(restTemplate!!.put("http://localhost:$port/sections/123", sectionAsJson
)).isNotNull
}
}
BTW: it is not a pretty good habit to expose your database ids, which is considered to be a security risk as it exposes your database layer. Instead, you might want to use a functional unique key ;)

Related

Kotlin OpenapiGenerator Any type generates into Map<String, JsonObject>

I'm struggling to find correct way to define Any object in Kotlin so that OpenApiGenerator would generate it as Object type.
I have a simple DTO object, payload is basically a map of object fields and values:
data class EventDto(
val payload: Map<String, Any>,
...other fields
)
Which gets converted to OpenAPI Spec and looks like this:
ommited code
...
"payload": {
"type": "object",
"additionalProperties": {
"type": "object"
}
},
But when I execute open-api-generator
this get's converted into Kotlin class looking like this:
#Serializable
public data class EventPayloadDto(
#SerialName(value = "payload")
val payload: kotlin.collections.Map<kotlin.String, kotlinx.serialization.json.JsonObject>? = null,
)
Which is not so nice convert into because each object value needs to be converted to JsonObject, is it possible to retain "Any" object when generating from OpenAPI docs or I must use Map<String, String>?
I tried using objectMapper.convert
objectMapper.convertValue(event, object : TypeReference<Map<String, JsonObject>>() {})
but since there are no serializers into JsonObject it had no effect and ended up in an error.

Jackson + KotlinModule: Conflicting/ambiguous property name definitions (implicit name 'isFoo')

Stumbling through a strange behaviour in Jackson when used with KotlinModule. Trying to deserialize a JSON object with isXxx-Boolean and xxx-none-Boolean property. Any solution how to deal with this?
data class FooObject(
#JsonProperty("isFoo")
val isFoo: Boolean,
#JsonProperty("foo")
val foo: String,
)
#Test
fun `deserialization should work` (){
val serialized = """
{
"isFoo": true,
"foo": "bar"
}
""".trimIndent()
val objectMapper: ObjectMapper = Jackson2ObjectMapperBuilder()
.modules(KotlinModule())
.build()
val deserialized = objectMapper.readValue(serialized, FooObject::class.java)
assertNotNull(deserialized)
}
throws
Results in
java.lang.IllegalStateException: Conflicting/ambiguous property name definitions (implicit name 'isFoo'): found multiple explicit names: [isFoo, foo], but also implicit accessor: [method org.dnltsk.Test$FooObject#getFoo()][visible=true,ignore=false,explicitName=false], [method org.dnltsk.Test$FooObject#isFoo()][visible=true,ignore=false,explicitName=false]
By removing the #JsonProperty-annotations the exception turns to
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Duplicate creator property "isFoo" (index 0 vs 1) for type `org.dnltsk.Test$FooObject`
at [Source: (String)"{
"isFoo": true,
"foo": "bar"
}"; line: 1, column: 1]
Add the following annotation to the top of your data class:
#JsonAutoDetect(
getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE,
)

How I can return my custom json file instead of default json file that generates spring boot?

I have a rest controller for authorization:
#RestController
class AuthController {
#PostMapping("/sign-up")
fun signUp(#RequestBody signUpRequest: SignUpRequest): ResponseEntity<String> {
some code here..
}
}
The signUp method gets SignUpRequest model as a request body. SignUpRequest model is:
enum class Role {
#JsonProperty("Student")
STUDENT,
#JsonProperty("Tutor")
TUTOR
}
data class SignUpRequest(
val role: Role,
val email: String,
val password: String
)
When I make /sign-up post request with JSON:
{
"role": "asdf",
"email": "",
"password": ""
}
It returns me an answer that were generated by spring boot:
{
"timestamp": "2020-02-12T05:45:42.387+0000",
"status": 400,
"error": "Bad Request",
"message": "JSON parse error: Cannot deserialize value of type `foo.bar.xyz.model.Role` from String \"asdf\": not one of the values accepted for Enum class: [Student, Tutor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `foo.bar.xyz.model.Role` from String \"asdf\": not one of the values accepted for Enum class: [Student, Tutor]\n at [Source: (PushbackInputStream); line: 3, column: 10] (through reference chain: foo.bar.xyz.model.SignUpRequest[\"role\"])",
"path": "/sign-up"
}
Question is: How I can return my custom JSON instead of that default generated JSON?
I want to return my custom JSON, like:
{
"result": "Invalid user data are given",
"errors": [
{
"fieldName": "ROLE",
"text": "Given role does not exist"
},
{
"fieldName": "EMAIL",
"text": "EMAIL is empty"
}
]
}
I suggest you to create ErrorContrller that generates custom json map as response. Then when you will catch an error in sign-up method, call ErrorContrllers method.
You can find info from this link
Finally I found out a solution. You should create a class that annotates #ControllerAdvice, and make a method that annotates #ExceptionHandler.
#ControllerAdvice
class HttpMessageNotReadableExceptionController {
#ExceptionHandler(HttpMessageNotReadableException::class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
fun handleException(
exception: HttpMessageNotReadableException
): PostSignUpResponseError {
val errors = mutableListOf<PostSignUpResponseErrorItem>()
errors.add(
PostSignUpResponseErrorItem(
fieldNamePost = "Role",
text = "Given role does not exist"
)
)
return PostSignUpResponseError(
result = "Invalid user data are given",
errors = errors
)
}
}
where PostSignUpResponseErrorItem and PostSignUpResponseError are:
data class PostSignUpResponseError(
val result: String,
val errors: List<PostSignUpResponseErrorItem>
)
class PostSignUpResponseErrorItem(
val fieldNamePost: PostSignUpRequestFieldName,
val text: String
)
Anyway, I still don't know how to attach this thing to a certain PostMapping method.

Optional query string enum parameter - openapi, springboot

I have an OpenApi spec:
paths:
/lessons:
get:
tags:
- lesson
operationId: getLessons
parameters:
- in: query
name: daysOfWeek
schema:
type: array
items:
$ref: '#/components/schemas/DaysOfWeekEnum'
Using swagger codegen this generates an endpoint like:
#ApiOperation(value = "Get a collection lessons", nickname = "getLessons", notes = "", response = LessonDto.class, responseContainer = "List", tags={ "lesson", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "List of Lessons", response = LessonDto.class, responseContainer = "List") })
#RequestMapping(value = "/lessons",
produces = { "application/json" },
method = RequestMethod.GET)
default ResponseEntity<List<LessonDto>> _getLessons(#ApiParam(removed for brevity) #Valid #RequestParam(value = "daysOfWeek", required = false, defaultValue="new ArrayList<>()") List<DaysOfWeekEnum> daysOfWeek) {
return getLessons(daysOfWeek);
}
I use TestRestTemplate in a test like so:
ResponseEntity<List<LessonDto>> lessonDtos =
testRestTemplate.exchange("/lessons", HttpMethod.GET, null,
new ParameterizedTypeReference<List<LessonDto>>() {
});
This url works:
/lessons?daysOfWeek=THURSDAY
These urls do not:
/lessons
/lessons?daysOfWeek=SOME_INVALID_VALUE
...and I get the following error:
nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
deserialize instance of java.util.ArrayList out of START_OBJECT
token
Any help appreciated.
In order to give you an empty list, you need to set the default value to be as an empty string:
#RequestParam(value = "daysOfWeek",
required = false,
defaultValue = "") List<DaysOfWeekEnum> daysOfWeek)
So the issue was related to an openapi-generator bug. Summary being:
When parameters of type array are added to an operation, the generated
Spring code includes an invalid defaultValue in the Spring MVC
parameter annotations
The fix was to upgrade to a later version of openapi-generator - 4.0.0 did the trick for me.
As an aside, the error message:
exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
deserialize instance of java.util.ArrayList out of START_OBJECT token
..was a bit of a red herring and it was actually TestRestTemplate related i.e. the ParameterizedTypeReference part. Changing this to String.class identified the true nature of the error.

how to store nested fields in elasticsearch

I am using Java High Level REST client. Here is the link to its documentation
I have created a client.
trait HighLevelRestClient {
def elasticSearchClient(): RestHighLevelClient = {
new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", ElasticSearchPort, "http")))
}
}
While indexing the data, the nested fields are being stored as String. The following code explains how the index is being created:
val indexRequest = new IndexRequest("my-index", "test-type").source(
"person", person,
"date", DateTime.now()
)
where, person is a case class, represented as:
Person(personId: String, name: String, address: Address)
and Address is itself a case class, represented as:
Address(city: String, zip: Int)
My application requires person to be stored as key-value pair, so that it's fields are searchable. But, when I am using the above code, it is being stored as String.
{
"person" : "Person(my-id, my-name, Address(my-city, zip-value))",
"date" : "2017-12-12"
}
and required structure is:
{
"person" : {
"personId" : "my-id",
"name" : "person-name",
"address": {
"city" : "city-name",
"zip" : 12345
}
},
"date" : "2017-12-12"
}
I hope I have framed the question well. Any help would be appreciated. Thanks!
You are almost there. To achieve your goal you need to:
Serialize the object to JSON on your side
Specify the content type of the request
It is actually described in the page of the Index API.
A convenient library to serialize case classes into JSON is for example json4s (you can see some examples of serialization here).
Your code might look like the following:
import org.apache.http.HttpHost
import org.elasticsearch.action.index.IndexRequest
import org.elasticsearch.client.{RestClient, RestHighLevelClient}
import org.elasticsearch.common.xcontent.XContentType
import org.joda.time.DateTime
import org.json4s.NoTypeHints
import org.json4s.jackson.Serialization
import org.json4s.jackson.Serialization.write
case class Address(city: String, zip: Int)
case class Person(personId: String, name: String, address: Address)
case class Doc(person: Person, date: String)
object HighClient {
def main(args: Array[String]): Unit = {
val client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9206, "http")))
implicit val formats = Serialization.formats(NoTypeHints)
val doc = Doc(
Person("blah1", "Peter Parker", Address("New-York", 33755)),
DateTime.now().toString
)
val indexRequest = new IndexRequest("my-index", "test-type").source(
write(doc), XContentType.JSON
)
client.index(indexRequest)
client.close()
}
}
Note that in this case:
new IndexRequest("my-index", "test-type").source(
write(doc), XContentType.JSON
)
this function will be used: public IndexRequest source(String source, XContentType xContentType)
While in your case:
new IndexRequest("my-index", "test-type").source(
"person", person,
"date", DateTime.now()
)
it will call public IndexRequest source(Object... source).
Hope that helps!

Resources