I am using Jackson (2.1.1) for JSON serialization/deserialization. I have an existing class with JAXB annotations. Most of these annotations are correct and can be used as-is with jackson. I am using mix-ins to slightly alter the deserialization/serialization of these classes.
In my ObjectMapper constructor I do the following:
setAnnotationIntrospector(AnnotationIntrospector.pair(
new JacksonAnnotationIntrospector(),
new JaxbAnnotationIntrospector(getTypeFactory())));
Based on the above, the Jackson annotations have precedence over Jaxb, because of the order of the introspectors. This is based on the Jackson Jaxb docs. For fields that I want ignored, adding #JsonIgnore to the field in the mix-in is working fine. There are a couple of fields that are marked as #XmlTransient in the existing classes that I do not want ignored. I have tried add #JsonProperty to the field in the mix-in, but it doesn't seem to work.
Here is the original class:
public class Foo {
#XmlTransient public String getBar() {...}
public String getBaz() {...}
}
Here is the mix-in:
public interface FooMixIn {
#JsonIgnore String getBaz(); //ignore the baz property
#JsonProperty String getBar(); //override #XmlTransient with #JsonProperty
}
Any idea how to resolve this without modifying the original class?
I also tested adding #JsonProperty to the members instead of using mix-ins:
public class Foo {
#JsonProperty #XmlTransient public String getBar() {...}
#JsonIgnore public String getBaz() {...}
}
I seem to get the same behavior as I did with the mix-in. Unless #XmlTransient is removed, the property is ignored.
The issue is that the AnnotationIntrospectorPair.hasIgnoreMarker() method basically ignores the #JsonProperty if either introspector detects an ignore marker:
public boolean hasIgnoreMarker(AnnotatedMember m) {
return _primary.hasIgnoreMarker(m) || _secondary.hasIgnoreMarker(m);
}
ref: github
A workaround is to subclass the JaxbAnnotationIntrospector to get the desired behavior:
public class CustomJaxbAnnotationIntrospector extends JaxbAnnotationIntrospector {
public CustomJaxbAnnotationIntrospector(TypeFactory typeFactory) {
super(typeFactory);
}
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
if ( m.hasAnnotation(JsonProperty.class) ) {
return false;
} else {
return super.hasIgnoreMarker(m);
}
}
}
Then just use the CustomJaxbAnnotationIntrospector in the AnnotationIntrospectorPair.
Related
Is is possible to exclude JsonProperties in the output of a Spring Boot Rest call based on a defined condition? (eg. the role of the user)
Example:
public class Employee{
#JsonProperty
private String name;
#JsonProperty
private String fieldForManagerOnly;
#JsonProperty
private String fieldForEmployeeOnly;
}
I want to have the fieldForManagerOnly only serialized in the JSON output when the user has the ROLE manager.
I've already tried the solution with the #JsonView (as described in Latest Jackson integration improvements in Spring) but that solution is very limited as the #JsonView is bound to one controler method and I want to have only one controller method.
I've solved the problem myself. I used the JsonView solution but instead of an annotation I select the JsonView from code.
First you need an interface for the Views.
public class JsonViews {
public interface EmployeeView {}
public interface ManagerView {}
}
Mark the fields in the Model class with the #JsonView annotations.
public class Employee{
#JsonProperty
private String name;
#JsonView(JsonViews.ManagerView.class)
private String fieldForManagerOnly;
#JsonView(JsonViews.EmployeeView.class)
private String fieldForEmployeeOnly;
}
In your controller set the JsonView to use based on the role (or some other condition):
#RequestMapping(value = "/{employeeId}", method = RequestMethod.GET)
public ResponseEntity<MappingJacksonValue> getEmployee(#PathVariable long employeeId) {
Employee employee = employeeService.getEmployee(employeeId);
MappingJacksonValue jacksonValue = new MappingJacksonValue(employeeResourceAssembler.toResource(employee));
if (getRole().equals("MANAGER")) {
jacksonValue.setSerializationView(JsonViews.ManagerView.class);
} else if (getRole().equals("EMPLOYEE")) {
jacksonValue.setSerializationView(JsonViews.EmployeeView.class);
}
return new ResponseEntity<>(jacksonValue, HttpStatus.OK);
}
Annotate the field with
#JsonInclude(JsonInclude.Include.NON_NULL)
and make sure to set the field fieldForManagerOnly to null if the current user is not a manager.
I've created this kind of class and I just serialized an instance with JsonConvert.SerializeObject(instance);
public sealed class MetadataList : Dictionary<string, Metadata>, IEnumerable<Metadata>
{
[JsonProperty("F")]
public FormatEnum Format { get; set; }
//...
}
The result was as I expect a serialized dictionary just the Format property wasn't presented. So I've marked the class as JsonObject.
[JsonObject]
public sealed class MetadataList
{
//...
}
Then the json contained all of the Dictionary<> base class's properties. So I tried to overwrite them just like this.
public sealed class MetadataList : Dictionary<string, Metadata>, IEnumerable<Metadata>
{
[JsonProperty("F")]
public FormatEnum Format { get; set; }
[JsonProperty("V")]
public new IEnumerable<Metadata> Values { get { return base.Values; } }
[JsonIgnore]
public new int Count { get { return base.Count; } }
[JsonIgnore]
public new IEqualityComparer<string> Comparer { get { return base.Comparer; } }
[JsonIgnore]
public new IEnumerable<string> Keys { get { return base.Keys; } }
//...
}
Than the json contained the union of the base class and my class properties except the ignored properties, so the json was contained the "Values":[...] and "V":[...] as well which are the same.
Meanwhile I was writing my class JsonConverter I realized the following.
JsonObjectContract contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
var properties = contract.Properties;
The contract.Properties contained the my class F and V JsonProperties and contained the base class Values, Count, Comparer and Keys JsonProperties and the internal SerializeObject
method iterate on this properties and serialize all of them.
I just want to know is that a bug in the serialization or this is the expected behavior? Because my point of view I have hided the base class properties so instead of those the properties should contain mine.
I managed it in my JsonConverter so everything is fine just I have to write more test what is not a problem.
If anybody knows how to ignore a base class property without JsonConverter then I would welcome the solution.
Thanks
I used the RestyGWT's JsonEncoderDecoder interface to encode/decode some objects. Among them there are instances of classes having properties not exposed using getter/setter methods. I tried annotating corresponding properties with org.codehaus.jackson.annotate.JsonProperty. But it's not working, causing error
[ERROR] [jsonsample] - field must not be private: com.mycompany.jsonsample.ItemList.items
com.mycompany.jsonsample.ItemList is the class with property items which has no getter/setter and annotated as said above.
Also is it possible to tell the encoder/decoder to skip some properties?
Example with private field and annotated constructor, you should provide more info on your problem though.
public abstract class Parent
{
#JsonCreator
public Parent(#JsonProperty("name") String name)
{
this.name = name;
}
#Override
public String getName()
{
return name;
}
private String name;
}
I'm facing problem with Jackson's ObjectMapper using JAXB annotations. To be concrete, I'm having collection with interface generic information and although I can deserialize input from XML, it is not possible with Jackson (using JAXB introspector). Maybe I'm just missing some configuration property or JAXB annotation? The problem is that "abstract types can only be instantiated with additional type information", I thought #XmlElementRef (or #XmlElement) with type information will handle this problem, but obviosly it does not.
Please note, that I want to stay only with JAXB annotations if possible.
E.g. using #JsonTypeInfo or #JsonDeserialize would be the last thing to do.
IEntry.java:
#XmlSeeAlso(Entry.class)
public interface IEntry {
String getValue();
}
Entry.java:
#XmlRootElement(name = "entry")
public class Entry implements IEntry {
#XmlElement(name = "value")
String value;
public Entry() {
}
public Entry(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
Aggregator.java:
#XmlRootElement(name = "aggregator")
public class Aggregator {
#XmlElementRef(type = Entry.class)
private Set<IEntry> entries;
public Aggregator() {
}
public Aggregator(Set<IEntry> entries) {
this.entries = entries;
}
public Set<IEntry> getEntries() {
return entries;
}
}
Test method:
#Test
public void testSerialization() throws Exception {
ObjectMapper om = new ObjectMapper();
AnnotationIntrospector intr = new JaxbAnnotationIntrospector();
om.getDeserializationConfig().withAnnotationIntrospector(intr);
String json = "{\"entries\":[{\"value\":\"X\"},{\"value\":\"Y\"},{\"value\":\"Z\"}]}\";\n}";
Aggregator agr = om.readValue(json, Aggregator.class);
}
Thanks for all response
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
I am not sure if Jackson supports this use case or not, but you appear to be using #XmlElementRef incorrectly. When you use #XmlElementRef the root element name associated with the class is used to determine the instance to be instantiated. If your example the node entries does not match the #XmlRootElement(name="entry") annotation.
You could try one of the following options (they all work with MOXy's JSON binding, see: http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html):
OPTION 1 - Change #XMLRootElement on Entry
#XmlRootElement(name = "entries")
public class Entry implements IEntry {
#XmlElement(name = "value")
String value;
public Entry() {
}
public Entry(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
OPTION #2 - Change the JSON Document
{"entry":[{"value":"X"},{"value":"Y"},{"value":"Z"}]}}
OPTION #3 - Use #XMLElement instead of #XMLElementRef
If you use the #XmlElement annotation you can specify on the field/property what the node name should be instead of relying on the #XmlRootElement annotation. Also if you annotate the fields you should specify #XmlAccessorType(XmlAccessType.FIELD) at the type level.
import java.util.Set;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "aggregator")
#XmlAccessorType(XmlAccessType.FIELD)
public class Aggregator {
#XmlElement(type = Entry.class)
private Set<IEntry> entries;
public Aggregator() {
}
public Aggregator(Set<IEntry> entries) {
this.entries = entries;
}
public Set<IEntry> getEntries() {
return entries;
}
}
For More Information
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-substitution.html
http://blog.bdoughan.com/2011/05/jaxb-and-interface-fronted-models.html
import org.codehaus.jackson.map.annotate.JsonDeserialize;
#JsonDeserialize(as = Entry.class)
public interface IEntry {
String getValue();
}
In a previous similar question, I asked about, how to serialise two different sets of fields using JacksonJson and Spring.
My use case is the typical Controller mapping with #ResponseBody annotation returning directly a particular object or collections of objects, that are then rendered with JacksonJson whenever the client adds application/json in the Accept header.
I had two answers, the first one suggests to return different interfaces with a different getter list, the second suggests to use Json Views.
I don't have problems to understand the first way, however, for the second, after reading the documentation on JacksonJsonViews, I don't know how to implement it with Spring.
To stay with the example, I would declare three stub classes, inside the class Views:
// View definitions:
public class Views {
public static class Public { }
public static class ExtendedPublic extends PublicView { }
public static class Internal extends ExtendedPublicView { }
}
Then I've to declare the classes mentioned:
public class PublicView { }
public class ExtendedPublicView { }
Why on earth they declare empty static classes and external empty classes, I don't know. I understand that they need a "label", but then the static members of Views would be enough. And it's not that ExtendedPublic extends Public, as it would be logical, but they are in fact totally unrelated.
And finally the bean will specify with annotation the view or list of views:
//changed other classes to String for simplicity and fixed typo
//in classname, the values are hardcoded, just for testing
public class Bean {
// Name is public
#JsonView(Views.Public.class)
String name = "just testing";
// Address semi-public
#JsonView(Views.ExtendedPublic.class)
String address = "address";
// SSN only for internal usage
#JsonView(Views.Internal.class)
String ssn = "32342342";
}
Finally in the Spring Controller, I've to think how to change the original mapping of my test bean:
#RequestMapping(value = "/bean")
#ResponseBody
public final Bean getBean() {
return new Bean();
}
It says to call:
//or, starting with 1.5, more convenient (ObjectWriter is reusable too)
objectMapper.viewWriter(ViewsPublic.class).writeValue(out, beanInstance);
So I have an ObjectMapper instance coming out of nowhere and an out which is not the servlet typical PrintWriter out = response.getWriter();, but is an instance of JsonGenerator and that can't be obtained with the new operator. So I don't know how to modify the method, here is an incomplete try:
#RequestMapping(value = "/bean")
#ResponseBody
public final Bean getBean() throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
JsonGenerator out; //how to create?
objectMapper.viewWriter(Views.Public.class).writeValue(out, new Bean());
return ??; //what should I return?
}
So I would like to know if anybody had success using JsonView with Spring and how he/she did. The whole concept seems interesting, but the documentation seems lacking, also the example code is missing.
If it's not possible I will just use interfaces extending each others. Sorry for the long question.
Based on the answers by #igbopie and #chrislovecnm, I've put together an annotation driven solution:
#Controller
public class BookService
{
#RequestMapping("/books")
#ResponseView(SummaryView.class)
public #ResponseBody List<Book> getBookSummaries() {}
#RequestMapping("/books/{bookId}")
public #ResponseBody Book getBook(#PathVariable("bookId") Long BookId) {}
}
Where SummaryView is annotated on the Book model like so:
#Data
class Book extends BaseEntity
{
#JsonView(SummaryView.class)
private String title;
#JsonView(SummaryView.class)
private String author;
private String review;
public static interface SummaryView extends BaseView {}
}
#Data
public class BaseEntity
{
#JsonView(BaseView.class)
private Long id;
}
public interface BaseView {}
A custom HandlerMethodReturnValueHandler is then wired into Spring MVC's context to detect the #ResponseView annotation, and apply the Jackson view accordingly.
I've supplied full code over on my blog.
You need to manually wire in the MappingJacksonHttpMessageConverter. In spring 3.1 you are able to use the mvc xml tags like the following:
<mvc:annotation-driven >
<mvc:message-converter>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
It is pretty ugly to not use spring 3.1, it will save you about 20 lines of xml. The mvc:annotation tag does ALOT.
You will need to wire in the object mapper with the correct view writer. I have noticed recently the using a #Configuration class can make complicated wiring like this a lot easier. Use a #Configuration class and create a #Bean with your MappingJacksonHttpMessageConverter, and wire the reference to that bean instead of the MappingJacksonHttpMessageConverter above.
I've manage to solve the problem this way:
Create custom abstract class to contain the json response object:
public abstract AbstractJson<E>{
#JsonView(Views.Public.class)
private E responseObject;
public E getResponseObject() {
return responseObject;
}
public void setResponseObject(E responseObject) {
this.responseObject = responseObject;
}
}
Create a class for each visibility (just to mark the response):
public class PublicJson<E> extends AbstractJson<E> {}
public class ExtendedPublicJson<E> extends AbstractJson<E> {}
public class InternalJson<E> extends AbstractJson<E> {}
Change your method declaration:
#RequestMapping(value = "/bean")
#ResponseBody
public final PublicJson<Bean> getBean() throws JsonGenerationException, JsonMappingException, IOException {
return new PublicJson(new Bean());
}
Create customs MessageConverter:
public class PublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{
public PublicApiResponseMessageConverter(){
super();
org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Public.class));
this.setObjectMapper(objMapper);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if(clazz.equals(PublicJson.class)){
return true;
}
return false;
}
}
public class ExtendedPublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{
public ExtendedPublicJsonMessageConverter(){
super();
org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.ExtendedPublic.class));
this.setObjectMapper(objMapper);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if(clazz.equals(ExtendedPublicJson.class)){
return true;
}
return false;
}
}
public class InternalJsonMessageConverter extends MappingJacksonHttpMessageConverter{
public InternalJsonMessageConverter(){
super();
org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Internal.class));
this.setObjectMapper(objMapper);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if(clazz.equals(Internal.class)){
return true;
}
return false;
}
}
Add the following to your xml:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="PublicJsonMessageConverter"></bean>
<bean class="ExtendedPublicJsonMessageConverter"></bean>
<bean class="InternalJsonMessageConverter"></bean>
</mvc:message-converters>
</mvc:annotation-driven>
That's it! I had to update to spring 3.1 but that's all. I use the responseObject to send more info about the json call but you can override more methods of the MessageConverter to be completely transparent. I hope someday spring include an annotation for this.
Hope this helps!