I've been scanning stack overflow all day and have not come across a viable solution for my problem.
I have a pojo that has primitive types and nested objects. For example...
#JsonIgnoreProperties({"duration", "errorCode", "haveFieldsChanged",
"serviceRequestToken", "storedProcDuration"}) // Abstract Base Class
properties
class Bus extends AbstractBaseClass implements Serializable{
#JsonIgnore
private static final long serialVersionId = 1;
#JsonProperty("name")
String name;
#JsonProperty("id")
int id;
#JsonProperty("students")
List<Student> students; // Nested Objects
#JsonProperty("employer")
Employer employer; //Nested object
// Getters and setters - none are annotated
#JsonRootName(value = "student")
class Student implements Serializable{
// student fields
}
#JsonRootName(value = "statusType")
class Employer implements Serializable{
#JsonProperty("id")
int id;
}
When I serialize my Bus object, jackson has no problem creating the proper structure for name, id, and my list of students. However, it will skip over Employer entirely leaving it absent from the json. See below.
{
"name":"Sean",
"id": 1,
"students":[student objects...]
}
I have tried #JsonProperty, #JsonSerialize(as = Employer.class), I tried building map for the employer object. I feel like I have exhausted most options. Is there something I am missing?
I ran into stack overflow exceptions trying some other annotations. I appreciate any help I can get.
The reason I added #JsonProperty to the fields is to help drive deserialization. I think this may be the root cause for serialization, but I am not certain.
Serialization Implementation
private String serializeBus(Bus bus) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
String json = null;
try {
json = mapper.writeValueAsString(bus);
} catch (JsonProcessingException e) {
logger.error("Error serializing bus");
throw new Exception(e);
}
return json;
}
Thank you!
So, if it is not feasible for you to share code, then I put your example above in a little sandbox project: https://github.com/mle-enso/stackoverflow
It currently runs with Spring Boot 2.1 but also downgrading the managed Jackson version(s) to 2.6.1 has no negative impact.
Maybe you could try this project with a simple mvn clean verify or a manual test run of de.mle.stackoverflow.jackson.BusSerializerTest to dig further into this issue here.
Related
I'm currently working on a SpringBoot API to interface with a MongoRepository, but I'm having trouble understanding how the JSON being passed becomes a Document for storage within Mongo. I currently have a simple API that stores a group of users:
#Document
#JsonInclude
public class Group {
#Id
#JsonView(Views.Public.class)
private String id;
#JsonView(Views.Public.class)
private String name;
#JsonView(Views.Public.class)
private Set<GroupMember> groupMembers = new HashSet<>();
}
There are also setter and getter methods for each of the fields, although I don't know how necessary those are either (see questions at the end).
Here is the straightforward component I'm using:
#Component
#Path("/groups")
#Api(value = "/groups", description = "Group REST")
public class Groups {
#Autowired
private GroupService groupService;
#GET
#Produces(MediaType.APPLICATION_JSON)
#ApiOperation(value = "Get all Groups", response = Group.class, responseContainer = "List")
#JsonView(Views.Public.class)
public List<Group> getAllGroups() {
return groupService.getAllGroups();
}
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#ApiOperation(value = "Create a Group", response = Group.class)
#JsonView(Views.Detailed.class)
public Group submitGroup(Group group) {
return groupService.addGroup(group);
}
}
Finally, I have a Service class:
#Service
public class GroupServiceImpl implements GroupService {
#Autowired
private GroupRepository groupRepository;
#Override
public Group addGroup(Group group) {
group.setId(null);
return groupRepository.save(group);
}
#Override
public List<Group> getAllGroups() {
return groupRepository.findAll();
}
}
The GroupRespository is simply an interface which extends MongoRepository<Group,String>
Now, when I actually make a call to the POST method, with a body containing:
{
"name": "group001",
"groupMembers": []
}
I see that it properly inserts this group with a random Mongo UUID. However, if I try to insert GroupMember objects inside the list, I receive a null pointer exception. From this, I have two questions:
How does SpringBoot (Jackson?) know which fields to deserialize from the JSON being passed? I tested this after deleting the getter and setter methods, and it still works.
How does SpringBoot handle nested objects, such as the Set inside the class? I tested with List instead of Set, and it worked, but I have no idea why. My guess is that for each object that is both declared in my class and listed in my JSON object, SpringBoot is calling a constructor that it magically created behind the scenes, and one doesn't exist for the Set interface.
Suppose I'm adamant on using Set (the same user shouldn't show up twice anyway). What tools can I use to get SpringBoot to work as expected?
It seems to me that a lot of the things that happen in Spring are very behind-the-scenes, which makes it difficult for me to understand why things work when they do. Not knowing why things work makes it difficult to construct things from scratch, which makes it feel as though I'm hacking together a project rather than actually engineering one. So my last question is something like, is there a guide that explains the wiring behind the scenes?
Finally, this is my first time working with Spring... so please excuse me if my questions are entirely off the mark, but I would appreciate any answers nonetheless.
I'm implementing RESTFul web service using Jersey 2.22.1 with MOXY as Json Provider.
For example I have the following entity User:
public class User {
private String id;
private String email;
private Address address;
private List<Phone> phones;
// getters & setters
}
and additional classes
public class Address {
private String type;
private String value;
// getters & setters
}
public class Phone {
private String type;
private String value;
// getters & setters
}
This is my JAX-RS resource implementation:
#POST
public Response create(User user) {
// some logic
}
Now when I'm sending POST request containting following json data:
{
"id":"qwe12",
"email":"emailname#g-mail.com",
"address":{
"type":"1WHEN-Honorable",
"value":"1WHEN-M"
},
"phones":[
{
"type":"HOME",
"number":"034-2342-12-31"
},
{
"type":"WORK",
"number":"31-21-3211-32"
}
]
}
it works perfectly, MOXY automatically maps this json to user object and it's fine
But I need to handle json with another level of nesting, like this:
{
"user":{
"id":"qwe12",
"email":"emailname#g-mail.com",
"address":{
"type":"1WHEN-Honorable",
"value":"1WHEN-M"
},
"phones":[
{
"type":"HOME",
"number":"034-2342-12-31"
},
{
"type":"WORK",
"number":"31-21-3211-32"
}
]
}
}
As you can see there is another key called user, and I know it's not a good json structure but it's a requirement and I have to accept it as it is. Now I need to be able to handle it. For now I can see only one solution.
I can add another one class wrapper aroung User and pass it to the create method.
So it would look this:
JAX-RS resource:
#POST
public Response create(UserWrapper user) {
// some logic
}
And java class:
public class UserWrapper {
private User user;
// getters & setters
}
It's working solution but I don't really like it because I need to add one more additional class. Would like to here your suggestions how to keep my java classes as it is and be able to accept json with one more level of nesting (i mean this user key).
Thanks in advance!
May not be the answer you're looking for, but I recommend using Jackson instead of MOXy. It's a more mature JSON framework with more features, and just works better. There may be a way with MOXy, but here is the Jackson way
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.22.1</version>
</dependency>
<!-- You need to remember to remove MOXy -->
In a ContextResolver, configure the ObjectMapper to unwrap the root value
#Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper mapper;
public MyObjectMapperProvider() {
mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
}
private static ObjectMapper createDefaultMapper() {
return mapper;
}
}
The value it will look for to deserialize will either be
The value in a #JsonRootName annotation, e.g. #JsonRootName("user") (on the class)
The value in a #XmlRootElement annotation, e.g. #XmlRootElment(name="user") (on the class)
If there is no annotation, then the name of the class, with the first letter lower cased.
Also not, unless you are using any MOXy specific features, making the switch to Jackson, you probably will not need to make any changes at all to your classes. Jackson also supports JAXB annotations (for the most part).
If you want the response to be wrapped, you can also use
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
I'm trying to serialize an object in Java using Jackson, but when I'm trying to serialize it, it gives me this error:
No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer
I tried this post, but it didn't help.
Here is the class I'm trying to serialize:
public class Repository {
public String name;
#JsonIgnore // to avoid recursive calls
public ArrayList<UserRole> contributors = new ArrayList<UserRole>();
public User self;
public ArrayList<FileInfo> files;
public RepositoryType repositoryType;
public String path;
}
I also tried to create getters/setters for each field but still nothing.
Here is my serialization method:
public static String convertObjectToJson(Object object) throws IOException {
ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = objectWriter.writeValueAsString(object); //error on this line
return json;
}
Looks like your one of your classes has java.io.FileDescriptor reference.
By default, Jackson will only work with with fields that are either public, or have a public getter methods – serializing an entity that has all fields private or package private will fail
If you look at the source code of java.io.FileDescriptor you can see
there are private fields without public getters.
You should configure your objectMapper visibility to allow access to private fields also.
// For jackson 2.*
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
// For jackson lower than 2
objectMapper.setVisibility(JsonMethod.FIELD, Visibility.ANY);
I was facing problems to send objects to Thymeleaf template with ResponseEntity it was giving me exception "StackOverFlowError" while serializing and your note " #JsonIgnore // to avoid recursive calls" solved my problem. Thanks
i'm using Jersey to build a REST service and as Json Processor i set Jackson in my application.
#javax.ws.rs.ApplicationPath("/")
public class MyApplication extends ResourceConfig {
public MyApplication() {
packages("controller");
register(JacksonFeature.class);
}
I implement a ContextResolver for Jacksons ObjectMapper (as it's suggested in this post Configure Jersey/Jackson to NOT use #XmlElement field annotation for JSON field naming) which creates an ObjectMapper that doesn't fail on unknown properties during deserialization:
#Provider
public class MyJsonObjectMapperProvider implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type)
{
System.out.println("mapper!!!");
ObjectMapper result = new ObjectMapper();
result.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return result;
}
}
and then i register this class in my application inserting register(MyJsonObjectMapperProvider.class) in the class MyApplication shown above. I obtain what i want, in sense that if there are unknown properties in the json the object mapper doesn't fail.
My problem is another; i have this class that i use to map a specified Json, in order to deserialize it and subsequently serialize it:
public class Version {
private String status;
private String updated;
private String id;
private List<Link> links;
#XmlElement(name = "media-types")
private List<MediaTypes> media_types;
//constructor + getter and setter
}
The problem is about the element media_types and the use of the annotation #XmlElement. Before i insert the ContextResolver to personalize ObjectMapper all works fine, in fact after serialization i obtain a json in which the element/attribute media_types has as name media-types; on the contrary with ContextResolver this element doesn't change it's name and has media_types. I think that, during serialization, the annotation XmlElement doesn't work, but i'm not sure that this is the correct reason.
Another attempt i try to do is to put #JsonProperty("media-types") annotation instead of #XmlElement annotation but with no result; in fact with this annotation i obtain also a Processing Exception.
The last attempt (in addition to what has been suggested by the previous post) was that of insert these lines of code in the ContextResolver:
AnnotationIntrospector intr = new AnnotationIntrospector.Pair(new JaxbAnnotationIntrospector(),new JacksonAnnotationIntrospector());
// usually we use same introspector(s) for both serialization and deserialization:
result.getDeserializationConfig().withAnnotationIntrospector(intr);
result.getSerializationConfig().withAnnotationIntrospector(intr);
in order to use both JaxbAnnotation and JacksonAnnotation but the name of the field in question remain media_types.
I hope i was clear in explain my problem and thanks you in advance for your help!
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 .
I solved it using #JsonBackReference for #ManyToOne binding and #JsonManagedReference on #OneToMany binding.
Thanks "Sotirios Delimanolis"
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.