Search code examples
mysqlreactjsspring-bootjpa

How to solve " no String-argument constructor/factory method to deserialize from String value (''")


I have this dummy project Spring Boot with React, every time I send a POST request it does NOT hit the database and generates below error

Error:-

 "JSON parse error: Cannot construct instance of `com.xx.xx.Category` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('4')"

I know it's the foreign key which seems to be the problem, I have been searching for a while threr are few similar threads but none of them are helping me.

POJO

@Data
@Entity
@Table(name = "product")
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
    private long prodId;
    private String prodName;
    private String prodDes;
    private int prodQunt;
    private Date createdDate = new Date();

    public Product() {}
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "catId")
    private Category cat;
}

Controller

@RestController
@RequestMapping("/prods")
@CrossOrigin(origins = "http://localhost:3000")
public class ProductController {

    @Autowired
    ProductRepository pr;
        
    @PostMapping("/add")
    public Product addProd(@RequestBody Product prod) {
        return pr.save(prod);
    }
}

Repository

public interface ProductRepository extends JpaRepository<Product, Long> {
}

React

const ProdAdd = () => {

const [product, setProduct] = useState({ prodName: "", prodDes: "", prodQunt: "" });
const [catList, setCatList] = useState([]);

useEffect(() => {
    fetch('http://localhost:8080/categories')
        .then((res) => res.json())
        .then(res => {
            setCatList(res)
        }).catch(err => console.log(err))
}, [])

const handleChange = (e) => {
    const name = e.target.name;
    const value = e.target.value;
    setProduct({ ...product, [name]: value });
}
const handleSubmit = async (e) => {
    e.preventDefault();
    console.log(product);
    await fetch("http://localhost:8080/prods/add", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(product)
    })
};

return (
    <div>
        <h1>Add New Product</h1>
        <Form method='Post' onSubmit={handleSubmit}>
            <Row>
                <Col>
                    <Form.Control name='prodName' id='' onChange={handleChange} value={product.prodName} placeholder='Product Name' />
                </Col>
                <Col>
                    <Form.Control name='prodDes' id='' onChange={handleChange} value={product.prodDes} placeholder='Product Description' />
                </Col>
                <Col>
                    <Form.Control name='prodQunt' id='' onChange={handleChange} value={product.prodQunt} placeholder='Product Quntity' />
                </Col>
                <Col>
                    <Form.Select name='cat' id='' onChange={handleChange} value={product.cat}>
                        {catList.map((cat) => {
                            return (
                                <option key={cat.id} value={cat.id}>
                                    {cat.catName}
                                </option>
                            );
                        })}
                    </Form.Select>
                </Col>
            </Row>
            <p></p>
            <Button variant="primary" type="submit">Submit</Button>
            <p></p>
        </Form>
    </div>
  );
};

export default ProdAdd;

Solution

  • From React, you are sending the id of the category but the REST endpoint is expecting a Category object. The Category constructor seems to exists but is not able to create the object with just the id as a String (what you are sending from React)

    You have 2 options here:

    1. Send the Category object as it should be.
    2. (Recommended) use a DTO instead of the Entity and update your endpoint

    In React, you have prodName, prodDes, prodQunt and cat as string. Create a Java object mapping those - let's say ProductDTO. Now, change the endpoint to

        @Autowired
        ProductRepository pr;
        @Autowired
        CategoryRepository cr;
    
        @PostMapping("/add")
        public Product addProd(@RequestBody ProductDTO prodDTO) {
            long catId = Long.parseLong(prodDto.getCat()); // should be validated before
            Category cat = cr.getOne(catId);
            Product prod = new Product();
            prod.setProdName(prodDTO.getProdName);
            prod.setProdDes(prodDTO.getProdDes);
            prod.setProdQunt(Integer.parseInt(prodDTO.getProdQunt)); // should be validated before
            prod.setCat(cat); // <-- updated
            return pr.save(prod);
        }