Question

I am making a restful application and trying to convert a list of objects into json for a specific url (@RequestMapping / @ResponseBody )

I have jackson-hibernate4 and jackson-core ,databind etc in my classpath.

Here is my object that i want to convert in json.

@Entity
@Table(name="Product")
public class Product {
@Id
@Column(name="productId")
@GeneratedValue
protected int productId;
@Column(name="Product_Name")
protected String name;

@Column(name="price")
protected BigDecimal baseprice;


@OneToMany(cascade = javax.persistence.CascadeType.ALL,mappedBy="product",fetch=FetchType.EAGER)
protected List<ProductOption> productoption = new ArrayList<ProductOption>();

@OneToMany(cascade = javax.persistence.CascadeType.ALL,mappedBy="product",fetch=FetchType.EAGER)
protected List<ProductSubOption> productSubOption = new ArrayList<ProductSubOption>();


@ManyToOne
@JoinColumn(name="ofVendor")
protected Vendor vendor;

The two objects inside Product are also POJO'S..

Here is my method that retrieves the list of product

@Override
public List<Product> getMenuForVendor(int vendorId) {
    List<Product> result = em.createQuery("from "+Product.class.getName()+" where ofVendor = :vendorId").setParameter("vendorId", vendorId).getResultList();
    System.out.println(result.size());
    return result;
}

When i try to return this list in my controller I was getting a "Cannot lazily load for json" so i set my objects to be fetched eagerly. Here is my controller

@Autowired
private MenuDaoImpl ms;

@RequestMapping(value = "/{vendorId}", method = RequestMethod.GET)
public @ResponseBody List<Product> getMenu(@PathVariable int vendorId){

    List<Product> Menu = Collections.unmodifiableList(ms.getMenuForVendor(vendorId));
    return Menu;
}

Now when i hit my url localhost:8080/getMenu/1 I should be getting a json string displayed but I get a big list of errors

WARN : org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver -       Handling of [org.springframework.http.converter.HttpMessageNotWritableException] resulted in Exception
  java.lang.IllegalStateException: Cannot call sendError() after the response has been     committed
at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:467)
 Could not write JSON: Infinite recursion (StackOverflowError) (through reference chain:

I am not sure if I am missing anything. Please guide .

Was it helpful?

Solution

I solved it using @JsonBackReference for @ManyToOne binding and @JsonManagedReference on @OneToMany binding.

Thanks "Sotirios Delimanolis"

OTHER TIPS

The question has already been answered. I am simply putting the link of a good example that clearly explains both the problem and the solution. http://geekabyte.blogspot.in/2013/09/fixing-converterhttpmessagenotwritablee.html

I realize this may not be 100% what you are after, but never the less, I felt like sharing it as I spent a lot of time fighting with this issue back in the days.

Also, instead of using the Json annotations, you could consider a custom Json parser. Make sure to use the correct package for the Jackson jars, as they recently changed their package structure (when you use any of their classes with the number 2 in it, like the ones below).

Start by creating a HttpMessageConverter:

   @Bean
    public HttpMessageConverter jacksonMessageConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setPrefixJson(false);
        converter.setPrettyPrint(true);
        converter.setObjectMapper(objectMapper());
        return converter;
    }

Add a ObjectMapper where you attach a mapping module and attach the serializers you will use.

public ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);

    SimpleModule module = new SimpleModule("jacksonJsonMapper", Version.unknownVersion());
    module.addSerializer(Product.class, new Product());

    objectMapper.registerModule(module);

    return objectMapper;
}

Now create a serializer. This class will provide the output you see when you fetch objects and Jackson will do the rest. You just provide the skeleton of how it should look.

public class Product erializer extends JsonSerializer<Product> {

@Override
public void serialize(Product product, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
    if(product == null) { 
        //Handle it, if you want 
    }

    if(product != null) {

        jsonGenerator.writeStartObject();

        jsonGenerator.writeStringField("id", productId.getId().toString());
        jsonGenerator.writeStringField("title",  product.getName());
        jsonGenerator.writeStringField("basePrice", product.getBasePrice());


        //Add items to the json array representation
        jsonGenerator.writeArrayFieldStart("productoptions");
        for(ProductOption productOption: product.getProductoption()) {
            jsonGenerator.writeStartObject("field", productOption.getFoo());

            jsonGenerator.writeEndObject();
        }
        jsonGenerator.writeEndArray();

        jsonGenerator.writeEndObject();
    }
}

}

On a side note, but one which I still hope will be useful: You need to make sure you have a transaction available when fetching entities lazily. You also should keep in mind that lazy is the preferable way of loading entities, unless you want to utterly crush your server every time you fetch any reference.

Try to change the method that fetch data by adding @Transactional on top of it, to make sure there is a transaction open for the method while its running, if not it will likely be closed by the time you try to fetch child objects.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top