javajsonspring-bootpostjsonb

JSON parse error: Cannot construct instance of `java.util.LinkedHashMap` no String-argument method to deserialize from String ('{"par1":"par2"}')]


I'm currently working on a spring boot e-commerce backend with frontend in Angular. This is the exact error message:

2024-11-22T15:47:36.290+01:00  WARN 12532 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `java.util.LinkedHashMap` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"par1":"par2"}')]

The error occurs when I try to add a new item to the store with parameters with field type Map<String, String>, which is deserialized wrong. All I have is this 400 (Bad Request) error.

enter image description here

I'd like to see the JSON sent in the request, but I cannot log anything in console from service to controller level.

    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity<Response> save(@RequestBody ProductFormDTO productFormDTO) {
        System.out.println("Received ProductFormDTO: " + productFormDTO);
        return productMediator.saveProduct(productFormDTO);
    }

The method seems to stop at the first line of the method, it doesn't even proceed to the print line.

In the ProductEntity the parameters field is defined like this:

    @Column(columnDefinition = "jsonb")
    private Map<String, String> parameters;

Same in productDTO and productFormDTO

    private Map<String, String> parameters;

Below I show my saveProduct method in mediator:

    public ResponseEntity<Response> saveProduct(ProductFormDTO productFormDTO) {
        try{
            ProductEntity product = formToProductEntity.toProductEntity(productFormDTO);
            categoryService.findCategoryByShortID(product.getCategory().getShortId()).ifPresentOrElse(product::setCategory,()->{
                throw new CategoryDontExistException();
            });
            productService.createProduct(product);
            return ResponseEntity.ok(new Response("Successful created a product"));
        }catch (RuntimeException e){
            e.printStackTrace();
            return ResponseEntity.status(400).body(new Response("Can't create product category don't exist"));
        }
    }

and service:

    @Transactional
    public void createProduct(ProductEntity product) {
        logger.info("Creating product with details: {}", product);

        if (product != null) {
            product.setCreateAt(LocalDate.now());
            product.setUid(UUID.randomUUID().toString());
            product.setActivate(true);
            productRepository.save(product);

            for (String uuid : product.getImageUrls()) {
                activateImage(uuid);
            }

            logger.info("Product created successfully with UID: {}", product.getUid());
            return;
        }
        throw new RuntimeException("Product is null, cannot create.");
    }

The logger doesn't work ofc.

Do you know what is the issue or do maybe how to see the exact JSON that is being sent in the request? It has probably some bad formatting. I tried the RequestLoggingFilter with @WebFilter("/*"), but it didn't work, I had nothing in console.

Also I am sure that the field parameters in database is set correctly to jsonb in PostgreSQL.

enter image description here

ps. It worked before, it has stopped working since I changed parameters field type from String to Map.


Solution

  • Ok, after debugging the request following this blog it turned out that the parameters were being sent as a string "parameters":"{\"par1\":\"par2\"}" not matching the jsonb format.

    I just changed the declaration of parameters in ProductFormDTO from

    private Map<String, String> parameters;
    

    to

    private String parameters;
    

    and deserialized it using ObjectMapper in ProductFormToProductEntity mapper

    ObjectMapper objectMapper = new ObjectMapper();
    productEntity.setParameters(objectMapper.readValue(productFormDTO.getParameters(), new TypeReference<Map<String, String>>() {}));