2

Our company is planning to switch our microservice technology to Spring Boot. As an initiative I did some advanced reading and noting down its potential impact and syntax equivalents. I also started porting the smallest service we had as a side project.

One issue that blocked my progress was trying to convert our Json request/response exchange to Spring Boot.

Here's an example of the code: (This is Nutz framework for those who don't recognize this)

@POST
@At // These two lines are equivalent to @PostMapping("/create")
@AdaptBy(type=JsonAdapter.class)
public Object create(@Param("param_1") String param1, @Param("param_2) int param2) {
    MyModel1 myModel1 = new MyModel1(param1);
    MyModel2 myModel2 = new MyModel2(param2);
    myRepository1.create(myMode12);
    myRepository2.create(myModel2);
    return new MyJsonResponse();
}

On PostMan or any other REST client I simply pass POST:

{
    "param_1" : "test",
    "param_2" : 1
}

I got as far as doing this in Spring Boot:

@PostMapping("/create")
public Object create(@RequestParam("param_1") String param1, @RequestParam("param_2) int param2) {
    MyModel1 myModel1 = new MyModel1(param1);
    MyModel2 myModel2 = new MyModel2(param2);
    myRepository1.create(myMode12);
    myRepository2.create(myModel2);
    return new MyJsonResponse();
} 

I am not sure how to do something similar as JsonAdapter here. Spring doesn't recognize the data I passed.

I tried this but based on the examples it expects the Json paramters to be of an Entity's form.

@RequestMapping(path="/wallet", consumes="application/json", produces="application/json")

But I only got it to work if I do something like this:

public Object (@RequestBody MyModel1 model1) {}

My issue with this is that MyModel1 may not necessarily contain the fields/parameters that my json data has.

The very useful thing about Nutz is that if I removed JsonAdapter it behaves like a regular form request endpoint in spring.

I couldn't find an answer here in Stack or if possible I'm calling it differently than what existing spring devs call it.

Our bosses expect us (unrealistically) to implement these changes without forcing front-end developers to adjust to these changes. (Autonomy and all that jazz). If this is unavoidable what would be the sensible explanation for this?

5 Answers 5

5

In that case you can use Map class to read input json, like

@PostMapping("/create")
public Object create(@RequestBody Map<String, ?> input) {
     sout(input.get("param1")) // cast to String, int, ..
}
Sign up to request clarification or add additional context in comments.

2 Comments

I"m going to give this a shot now.
I confirm this works as I wanted. Thanks for this. I'll continue exploring possibilities with this. One last thing though. What else can I do with the RequestBody aside from Map?
2

I actually figured out a more straightforward solution.

Apparently this works:

@PostMapping("/endpoint")
public Object endpoint(@RequestBody MyWebRequestObject request) {
    String value1 = request.getValue_1();
    String value2 = request.getValue_2();
}

The json payload is this:

{
   "value_1" : "hello",
   "value_2" : "world"
}

This works if MyRequestObject is mapped like the json request object like so. Example:

public class MyWebRequestObject {
   String value_1;
   String value_2
}

Unmapped values are ignored. Spring is smart like that.

I know this is right back where I started but since we introduced a service layer for the rest control to interact with, it made sense to create our own request model object (DTOs) that is separate from the persistence model.

Comments

1

try this:

Add new annotation JsonParam and implement HandlerMethodArgumentResolver of this, Parse json to map and get data in HandlerMethodArgumentResolver

{
 "aaabbcc": "aaa"
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonParam {
    String value();
}

@Component
public class JsonParamMethodResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(JsonParam.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        RepeatedlyRequestWrapper nativeRequest = webRequest.getNativeRequest(RepeatedlyRequestWrapper.class);
        if (nativeRequest == null) {
            return null;
        }

        Gson gson = new Gson();
        Map<String, Object> response = gson.fromJson(nativeRequest.getReader(), new TypeToken<Map<String, Object>>() {
        }.getType());

        if (response == null) {
            return null;
        }

        JsonParam parameterAnnotation = parameter.getParameterAnnotation(JsonParam.class);
        String value = parameterAnnotation.value();
        Class<?> parameterType = parameter.getParameterType();
        return response.get(value);
    }
}
@Configuration
public class JsonParamConfig extends WebMvcConfigurerAdapter {
    @Autowired
    JsonParamMethodResolver jsonParamMethodResolver;
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(jsonParamMethodResolver);
    }
}
@PostMapping("/methodName")
public void methodName(@JsonParam("aaabbcc") String ddeeff) {
    System.out.println(username);
}

Comments

0

You can use @RequestBody Map as a parameter for @PostMapping, @PutMapping and @PatchMapping. For @GetMapping and @DeleteMapping, you can write a class which implements Converter to convert from json-formed request parameters to Map. And you would register that class as a bean with @Component annotation. Then you can bind your parameters to @RequestParameter Map.

Here is an example of Converter below.

@Component
public class StringToMapConverter implements Converter<String, Map<String, Object>> {

    private final ObjectMapper objectMapper;

    @Autowired
    public StringToMapConverter(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public Map<String, Object> convert(String source) {
        try {
            return objectMapper.readValue(source, new TypeReference<Map<String, Object>>(){});
        } catch (IOException e) {
            return new HashMap<>();
        }
    }

}

If you want to exclude specific field of your MyModel1 class, use @JsonIgnore annotation onto the field like below.

class MyModel1 {
     private field1;
     @JsonIgnore field2;
}

Then, I guess you can just use what you have done.(I'm not sure.)

public Object (@RequestBody MyModel1 model1) {}

4 Comments

My goal is to AVOID using a model in the method parameters (I want native data types). The JsonIgnore parameter is if I don't want to use a field. My problem is the method signature might contain parameters that model doesn't have.
I'm sorry for my misunderstanding. I think the only thing you can do without @RequestBody MyModel1 is to write all required parameters with @RequestParam. However, can't you achieve your goal by using a class properly implements Converter?
I’m actually fine if what I am expecting from spring can’t be done. It’s just that I have to have a good reason to tell my bosses why not. ;)
I see. You have already known a good reason by experiencing those, don't you? Actually I don't know about Nutz and exact logic performed by JsonAdapter, but @RequestParam can bind one parameter and @RequestBody can bind all parameters along with spring framework. I'm afraid of doing no help. Good luck!
0

i think that you can use a strategy that involve dto https://auth0.com/blog/automatically-mapping-dto-to-entity-on-spring-boot-apis/

you send a json to your rest api that is map like a dto object, after you can map like an entity or use it for your needs

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.