I am rewriting an old REST service written in an in-house framework to use Spring. I have a Controller with a POST method which takes a parameter either as a POST or as x-www-form-urlencoded body. Following multiple StackOverflow answers, I used #ModelAttribute annotation and created a model.
My problem is that the old REST API is using a property name in snake case - say some_property. I want my Java code to follow the Java naming conventions so in my model the field is called someProperty. I tried using the #JsonProperty annotation as I do in my DTO objects but this time this didn't work. I only managed to make the code work if the field in the model was named some_property. Here is my example code:
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
#RestController
#RequestMapping("/my/api/root")
public class SomethingController {
#PostMapping("/my/api/suffix")
public Mono<Object> getSomething(
#RequestParam(name = "some_property", required = false) String someProperty,
#ModelAttribute("some_property") Model somePropertyModel) {
// calling my service here
}
public class Model {
#JsonProperty("some_property")
private String someProperty;
private String some_property;
// Getters and setters here
}
}
I am searching for annotation or any other elegant way to keep the Java naming style in the code but use the legacy property name from the REST API.
The #JsonProperty annotation can only work with the JSON format, but you're using x-www-form-urlencoded.
If you can't change your POST type, you have to write your own Jackson ObjectMapper:
#JsonProperty not working for Content-Type : application/x-www-form-urlencoded
I also met a similar case you,
Please replace #ModelAttribute("some_property") with #RequestBody.
Hope to help you!
Related
I'm learning how to create a RESTful API with Springboot, and the #Value tag is not injecting values from application.properties, nor is it injecting values specified in the tag itself. I have my project structured as such:
api
config
controllers
model
services
SpringApplication.java
resources
application.properties
Strangely, this behavior only seems to occur within files located in my "services" folder. The #Value tag works as expected in files located in my "controllers" folder. Below are examples of what I am doing:
#Value("${var}")
String variable
"var" is defined in application.properties as var=some_stringbut variable is still initialized as 'null'
#Value("I am directly assigning a value to this variable, but it still comes out null")
String variable
I believe I am using the correct import: import org.springframework.beans.factory.annotation.Value.
At first I just thought the "services" folder was blind to the directory where application.properties is located, but after trying to directly inject values, I'm not so sure what to think.
Edit
All of the classes in the services folder are annotated with #Service and nothing else. Below is what the class looks like. I've opted to leave out implementations of the methods, other variables, and irrelevant imports. The code/methods all work as expected when hard-coding the variables. My focus is the #Value tag.
package myapi.api.services;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
#Service
public class MyService {
#Value("${var}")
String variable;
public List<Data> getData() {
return new ArrayList<Data>();
}
public void postData() {
}
Edit 2
Below is the APIController class, stored in the "controllers" folder. Again, I've opted to leave out irrelevant methods/imports/variables. I would also like to note, that the #Value tag works as expected in this class.
package myapi.api.controllers;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import myapi.api.services.MyService;
import lombok.RequiredArgsConstructor;
#RestController
#RequestMapping("api")
#RequiredArgsConstructor
public class APIController {
#Autowired
private final static MyService myService = new MyService();
#GetMapping("/getdata")
public List<Data> getData() {
return myService.getData();
}
}
#Autowired
private final static MyService myService = new MyService();
Three issues:
First, you cannot have a new operator. Spring controls the lifecycle of your class instances. It will call new for you in the background. Remove the entire new operator stanza.
Next: your your field cannot be final. After construction of the class, Spring will need to modify that field with a proxy. Remove the final declaration;
Finally: your field cannot be static. static variables have a certain lifecycle with the JVM, and you need to let the Spring framework manage your lifecycle. remove the static operator.
The correct declaration should be:
#Autowired
private MyService myService;
I am writing a process that needs to pull JSON data from an API and provide it to another system that requires the field names to be completely lowercased. I have attempted to utilize the built in LowerCaseStrategy but this does not work. An example of what I have tried is:
package com.example
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
public class Example {
private static final ObjectMapper mapper = new ObjectMapper();
public Example(){
mapper.setPropertyNamingStrategy(new PropertyNamingStrategies.LowerCaseStrategy());
}
public JsonNode fetchData(String url) throws MalformedURLException, IOException{
JsonNode data = mapper.readTree(new URL(url));
return data;
}
}
A PropertyNamingStrategy affects how JSON property names are mapped from methods and fields in a Java class. This code doesn't do any mapping to Java objects, it only deserializes JSON into a JsonNode. In that case, PropertyNamingStrategy doesn't apply, and the names are retained from the original JSON source.
I was able to find the following gist which is working for me https://gist.github.com/stmcallister/92d0b4c2355a490ffed008cfbda69063
There's probably a better solution out there but this is perfect for my use case.
My question is regarding the use of the interface class. I am fairly new to Spring so please bear with me if this is overly simple.
First of all, what is the point of having an IBoxService interface here when you could just declare the find all in BoxService. Secondly, in the controller how is IBoxService being used. Meaning, we are calling IBoxService.findAll(). But, how is this being tied to the BoxService class. What if multiple service classes implemented IBoxService? Is this a java thing or a Spring injection thing. Thanks.
package com.xyz.service;
import com.xyz.model.Box;
import java.util.Set;
public interface IBoxService {
Set<Box> findAll();
}
package com.xyz.service;
import com.xyz.model.Box;
import com.xyz.repository.BoxRepository;
import java.util.Set;
import org.springframework.stereotype.Service;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
#Service
#AllArgsConstructor
#Slf4j
#Transactional
public class BoxService implements IBoxService {
#Autowired
private BoxRepository boxRepo;
#Override
public Set<City> findAll() {
return (Set<City>) repository.findAll();
}
}
package com.xyz.controller;
import com.xyz.model.Box;
import com.xyz.service.IBoxService;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping("/api/box")
public class BoxController {
#Autowired
private IBoxService boxService;
#GetMapping
public ResponseEntity<Set<Boxes>> allBoxes() {
return (Set<Box>) boxService.findAll();
}
}
There are various reasons why Service layer interfaces are created. The first and most important reason is testability. You can create mocks of service interface and test your code easily, if you cannot create mocks using a mocking library then you can create test stubs.
One more reason is, we can achieve loose coupling between Controller and Service layer. Suppose you want to entirely change the implementation of service, you can create new service implementation and inject that implementation by injecting new bean by qualifier name
Please understand basic Java and use of interface . Spring boot is just abstraction over Java hence all the basic concepts applies as it is.
Coming back to your questions IBoxService is a interface which allows to inject required implementation of it at controller level. As of now only implementation of IBoxServic is BoxService hence it is getting injected automatically. In case you have multiple implementations you need to use qualifier annotation to specify kind of implementation you need to inject. Or you can create object bu yourself using class names
Consider below:
IBoxService is implemented by two classes BoxService and TiffinBoxService
Now in controller you can inject implementation which you want. Which allow us to achieve principle of interface which is hide internal details.
User which is controller in this case doesn't need to know which class is being use internally as we are using reference of interface.
List interface is best example which has ArrayList and LinkedList as implementation classes.
Hope it is useful !!
My company has a Java web service using Spring Web that accepts JSON via a REST API. We're using Maven and our Jackson version is 2.9. We're trying to prevent deserialization exceptions from being thrown when an integrator passes in an empty list when our API isn't expecting one.
For example, here's my application class:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
#EnableWebMvc
#SpringBootApplication
public class ExampleApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
Student.java class:
import lombok.Data;
import java.util.Map;
#Data
public class Student {
private String firstName;
private String lastName;
private Map<String, String> classGrades;
}
A StudentController:
package com.example.example.controllers;
import com.example.example.models.Student;
import org.springframework.http.RequestEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.stream.Stream;
#RestController()
#RequestMapping(value = "/Students/", produces = "application/json")
public class StudentController {
#PostMapping(path = "")
public RequestEntity<Student> createNewStudent(#RequestBody Student student) {
return null;
}
}
The application.properties:
spring.jackson.deserialization.accept-empty-array-as-null-object=true
The pom.xml contains all the default dependencies, with the following added:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
Every other project file is default to the project structure generated by the Spring initializer. The expected request body (JSON formed using Postman):
{
"firstName": "Mark",
"lastName": "Twain",
"classGrades": {
}
}
Works just fine. However, if any field (though in our specific case, the classGrades field) receives an empty list, a Jackson deserialization exception is thrown. An example JSON request that fails:
{
"firstName": "Mark",
"lastName": "Twain",
"classGrades": []
}
And the exception that is thrown:
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.util.LinkedHashMap` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.LinkedHashMap` out of START_ARRAY token
at [Source: (PushbackInputStream); line: 65, column: 28]
According to the project's github page, the option ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT should resolve this issue for us. We've tried setting this directly on an ObjectMapper configuration object and within application.properties using the line:
spring.jackson.deserialization.accept-empty-array-as-null-object=true
Neither method seemed to take effect. We're currently using a workaround using the #JsonDeserialize(using = MyCustomDeserializer.class) annotaion on fields prone to this problem. However, we would like to be able to have all of our fields treat empty lists as null by default.
Are we misunderstanding the configuration option and using it incorrectly? Is there a way to treat empty lists as null within our app, and if so, how can we accomplish this?
For this example case, my problem was the #EnableWebMvc annotation within the ExampleApplication class. Removing that annotation allowed me to successfully send an empty array to my endpoint, which then received it as a null object.
Note
My original problem still exists within my company's application, even after removing the annotation. However, it seems like this may be an issue with a different setting that might be clashing with ...accept-empty-arrays-as-null-object.
A Map is serialized as
{ "k1": "v1", "k2": "v2"}
An empty Map is serialized as
{ }
In your example you try to put a [], which represents an empty list or array, into a Map and that is why Jackson complains. Simply, cannot map an empty list into a Map.
This also explains why enabling ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT has no effect
Update
I was not right. Jackson can map empty array to a Map.
Created a minimal springboot example that uses the spring.jackson.deserialization.accept-empty-array-as-null-object=true property and can map an empty array [] to the Map
Check here if you want.
I'm new to java code generation from WADL, I used cxf-wadl2java-plugin and it works fine, the problem is that the generated code contains the resource code but no implementation as such:
/**
* Created by Apache CXF WadlToJava code generator
**/
package wadl.client;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import wadl.Response;
#Path("/path")
public class PathResource {
#GET
#Produces({"application/xml", "application/json" })
public Response get(#QueryParam("q") String q, #QueryParam("attr") String attr) {
//TODO: implement
return null;
}
}
Is there any mean to get an implementation when the code generation is done?
How do I us this class (injection??)?
How do I set the data return type? (json, xml, Response object?)
Thanks for your help
According to the Wikipedia Page:
WADL models the resources provided by a service and the relationships
between them
It however, does not state anything on the logic behind such resources.
Long story short, the WADL should explain/point out what resources must your system expose, however it does not explain how are these resources implemented, which is usually ideal since Web Services are usually used to simply expose a set of functionalities.