Algorini
Algorini

Reputation: 874

spring serialize autowired field

I have an issue serializing a spring managed bean.

I want to return an autowired bean as the response for a restcontroller. I have read several responses, one of which advises using a simpleFilter.(Use SimpleFilter to exclude non required fields.). However I do not think this suggestion is very practical, and moreover, I am sure there is a much more simple and concrete way to solve the problem.

I have a spring managed bean called JobStatus.

@Component
@Scope(value="Prototype")
public class JobStatus{

    private Integer job_type;

    public Integer getJob_type() {
        return job_type;
    }

    public void setJob_type(Integer job_type) {
        this.job_type = job_type;
    }


    public JobStatus(){

    }
}

I have a controller as follows:

@RestController
public class JobController  {

@Autowired 
JobStatus js;

@RequestMapping(value = "/get_job_status", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody
JobStatus get_job_status(@RequestBody JobStatusRequest  req) {


    js.setJobType(req.getJobType);


    ObjectMapper mapper = new ObjectMapper();

    try {
        System.out.println(mapper.writeValueAsString(js));
    } catch (JsonProcessingException e) {

        e.printStackTrace();
    }



    return js;

}

}

It throws the following exception:

com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class org.springframework.cglib.proxy.NoOp$1 and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: ATM.Job.JobStatus$$EnhancerBySpringCGLIB$$be675215["callbacks"])

I have tried changing the scope of JobStatus to "singleton" and "session" and "request" and it doesn't make any difference. How are we supposed to serialize "proxies"?

Upvotes: 2

Views: 2252

Answers (4)

badbishop
badbishop

Reputation: 1668

You can just tell Jackson: "serialize my class using the very same type as supertype". Since Spring proxies subclass your original class, this seem to work at least on Spring Boot 2.0.4.RELEASE:

@JsonSerialize(as=MyCompontClass.class)
@Component
public class MyCompontClass{
    // fields, getters, setters
}

Jackson API docs say:

as

public abstract Class as

Supertype (of declared type, which itself is supertype of runtime type) to use as type when locating serializer to use.

Upvotes: 5

avogt
avogt

Reputation: 81

If it is acceptable for you to only have fields reachable via public getter method getting serialised, you can configure Jackson to ignore the non-public fields. This results in the proxy fields not being serialised:

  • add a @Configuration bean somewhere on your class path in a package under where the Application.java class for spring resides:
  • in there set ObjectMapper properties

    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.PUBLIC_ONLY);

Here is a complete class:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import 
 org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import
 org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

@Configuration
public class JacksonConfig extends WebMvcConfigurerAdapter {

    @Bean
    @Primary
    public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        setup(objectMapper);
        return objectMapper;
    }

    public void setup(ObjectMapper objectMapper) {

        objectMapper.setVisibility(PropertyAccessor.ALL,                    
                            JsonAutoDetect.Visibility.NONE);
        objectMapper.setVisibility(PropertyAccessor.GETTER, 
                            JsonAutoDetect.Visibility.PUBLIC_ONLY);

    }

    @Override
    public void configureMessageConverters(
                  List<HttpMessageConverter<?>> converters) {
        final MappingJackson2HttpMessageConverter converter = 
              getMappingJackson2HttpMessageConverter();

        converters.add(converter);
        super.configureMessageConverters(converters);
    }

    @Bean
    @Primary
    public MappingJackson2HttpMessageConverter
 getMappingJackson2HttpMessageConverter() {
        final MappingJackson2HttpMessageConverter converter = new 
                  MappingJackson2HttpMessageConverter();
        final ObjectMapper objectMapper = new ObjectMapper();

        setup(objectMapper);

        converter.setObjectMapper(objectMapper);
        converter.setPrettyPrint(true);
        return converter;
    }
}

Upvotes: 0

Crane
Crane

Reputation: 151

I am not sure if this will work but it worked for me in another context. You can try using @Configurable on your Jobstatus class(with AspectJ weaving configured) and create a new instance of job status in the controller. Spring would inject the bean whenever JObStatus's new instance is called. You can then serialize the jobstatus object as usual.

Upvotes: 0

dbrown0708
dbrown0708

Reputation: 4764

Create a view class

public class JobStatusView {

    public JobStatusView(JobStatus js) {
        job_type = js.getJob_type();
    }

    private Integer job_type;

    public Integer getJob_type() {
        return job_type;
    }

    public void setJob_type(Integer job_type) {
        this.job_type = job_type;
    }
}

Have your controller method return new JobStatusView(js) or create a Factory class or whatever your preferred method for creating instances is.

This has the benefit of separating the data from the view. You can add whichever Jackson annotations on the view class later, if the need arises, without having to pile them into the original bean.

Upvotes: 1

Related Questions