Can the JsonProperty required value be conditional? - java

Is it possible to have the #JsonProperty required dynamically set or set at call?
The reason behind this...
I'm generating json files which describes a schema and defines
What are the required fields for a new item
What are the required fields for an update to an item.
So, a creation requires only foo
and an update requires foo and bar
Can I make things so I can pass in something to say bar is now required?
or would I need to duplicate this code in order to have different settings for JsonProperty?
#JsonInclude(Include.NON_NULL)
public class Bean {
#JsonProperty(value="foo", required=false)
private FooProperty fooProperty;
#JsonProperty(value="bar", required=false)
private BarProperty barProperty;
//
public FooProperty getFooProperty() { return fooProperty; }
public void setFooProperty(FooProperty argFooProperty) {
this.fooProperty = argFooProperty
}
public BarProperty getBarProperty() { return barProperty; }
public void setFooProperty(BarProperty argBarProperty) {
this.barProperty = argBarProperty
}
}

You could solve this issue in couple of ways. First one would be as Franjavi suggested you could use a mixin.
Have a mixin class which will mark your foo as one of the ignored properties. In your main class mark both the fiends required and you can inject this mixin whenever this is an option field.
#JsonIgnoreProperties("foo")
public abstract class mixinClass {
}
You configure your mixin into your mapper as follows.
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Bean.class, mixinClass.class);
This is not always a working option whenever you are getting this response from a third party API where you might be serializing/deserializing using the default jackson mapper which are provided by them like Resttemplate.
In this case, instead of having the foo property, you can just included whatever the properties that are present in all the responses and handle the rest of the properties using #JsonAnyGetter and #JsonAnySetter. You can capture this in a map with key being your object name which is in this case foo. You need to have the following part in your parent node or whichever node that will encapsulates these optional properties.
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
Let me know if you need any further clarification!!

You can play wiht Mixins and the mapper:
public interface BarRequiredMixIn {
#JsonProperty(value="bar", required=true)
private BarProperty barProperty;
}
ObjectMapper mapper = new ObjectMapper();
// When you want that it is required
mapper.addMixInAnnotations(Bean.class, BarRequiredMixIn.class);
But I strongly recommend to avoid to use this kind of conditional restrictions with Jackson, since it is not designed for it, and it looks kind of confusing.

Based on the example you have given, setting the following in your POJO would be sufficient & Jackson would be able to figure out whether to deserialize it/not . All #JsonProperty are required by default, so with the code below, you would be able to achieve optional bar value in your Create/Update scenarios based on payload in the request
#JsonProperty(value="bar", required=false)
private Bar bar;

Related

Spring Framework JSON Serialization to Array rather than Object

In my web application that is using Spring, we want use a custom JSON structure. Spring by default takes a POJO like this:
public class Model {
private int id;
private String name;
public Model(){}
public Model(int id, String name){
this.id = id;
this.name = name;
}
}
and turns it into this:
{"id":1, "name":"Bob"}
With our application, we want to turn it into this instead:
[1, "Bob"]
I want to use Spring's default serialization logic that detects the Java type (int, String, Collection, etc.) and maps to the appropriate JSON type, but just change the wrapping object to an array rather than and object with fields.
This is the Serializer I have so far (which will be implemented in the model with #JsonSerialize(using = Serializer.class)), but would prefer not to rewrite all the logic Spring already has implemented.
public class Serializer extends JsonSerializer<Model> {
#Override
public void serialize(Model value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartArray();
jgen.writeString(value.id);
.... other values ...
jgen.writeEndArray();
}
}
How can I hook into the pre-existing Serializer so that this new serializer will work with any POJO as the default one does (not just the Model class, but any similar or child class we need to serialize to an array)? This could have mixed properties and no specific naming convention for the properties.
I want to avoid writing a custom serializer for every different Model class (the ... other values ...) section.
Take a look at Apache BeanUtils library, in particular, pay attention to the BeanUtils.populate() method.
What that method does is to convert any given Object to a Map<String, Object>, based on JavaBeans conventions. In the keys you'd have the attribute names, while in the values you'd have every attribute's value. That method should be enough for standard cases. Read the documentation carefully, to check how to handle special cases.
Model model = ...; // get your model from some place
Map<String, Object> properties = new HashMap<>();
BeanUtils.populate(model, properties);
// Exception handling and special cases left as an excercise
The above recursively fills the properties map, meaning that if your Model has an attribute named otherModel whose type is OtherModel, then the properties map will have another map at the entry that matches the otherModel key, and so on for other nested POJOs.
Once you have the properties map, what you want to serialize as the elements of your array will be in its values. So, something like this should do the job:
public List<Object> toArray(Map<String, Object> properties) {
List<Object> result = new ArrayList<>();
for (Object obj : properties.values()) {
Object elem = null;
if (obj != null) {
Class<?> clz = obj.getClass();
if (Map.class.isAssignableFrom(clz)) {
elem = toArray((Map<String, Object>) obj); // recursion!
} else {
elem = obj;
}
}
result.add(elem); // this adds null values
// move 1 line up if you don't
// want to serialize nulls
}
return result;
}
Then, after invoking the toArray() method, you'd have a List<Object> ready to serialize using the standard Spring mechanisms. I even believe you won't need a specific serializer:
List<Object> array = toArray(properties);
return array; // return this array, i.e. from a Controller
Disclaimer:
Please use this as a guide and not as a final solution. I tried to be as careful as possible, but the code might have errors. I'm pretty sure it needs special handling for arrays and Iterables of POJOs. It's undoubtedly lacking exception handling. It works only for POJOs. It might explode if the supplied object has circular references. It's not tested!
You could use #JsonValue annotation for this.
Example:
public class Model {
private int id;
public Model(){}
public Model(int id){
this.id = id;
}
#JsonValue
public int[] getValue() {
return new int[]{this.id};
}
}

Jackson: how to prevent field serialization (while keeping deserialization)

I'd like to go further on what this question was about, I've been roaming SO for a solid hour now without finding anything.
Basically, what I'm trying to do is having a property properly instanciated through Jackson internal reflection algorithm during deserialization but having this same property not serialized when it comes to serialization.
I know about #JsonIgnore and #JsonIgnoreProperties but apparently I can't seem to use them right : either my property is correctly deserialized when I feed Jackson a proper map of properties but it also appears in the serialized results, either (when using #JsonIgnore) it is not serialized (which is wanted) but also not deserialized (not wanted).
Example :
public class Foo {
/* This is the property I want to be instanciated by Jackson upon deserialization
* but not serialized upon serialization
*/
private final Object bar = null;
public Object getBar() {
return bar;
}
}
To make things worse, as you can see, the property is final (this is why I'm keen on using Jackson reflection ability upon Foo instanciation through deserialization). I've read on potential solution about annotating the setter and the getter differently but I'd like to keep this property final if possible. If not possible, I'd settle for a non-final property.
I would appreciate answers not suggesting custom serializer/deserializer, my code base is currently free of such and if the solution could be of minimal impact, that would be perfect. Again, I'm no Jackson expert so if what I'm asking is not possible I'll obviously accept alternative answers.
I've also read this thread on github but none of the suggested ways of implementation have actually been implemented at the moment.
Thanks
EDIT : to make things clearer
public class Foo {
private final String bar = null;
public String getBar() {
return bar;
}
#Override
public String toString() {
return bar;
}
}
public void testMethod() throws IOException {
String json = "{\"bar\":\"Value\"}";
ObjectMapper mapper = new ObjectMapper();
Foo foo = mapper.readValue(json, Foo.class);
System.out.println(foo); // should have a bar property set to "Value"
System.out.println(mapper.writeValueAsString(foo)); // should return an empty JSON object
}
I am not sure whether it is elegant solution but you can use MixIn feature. You have to create new interface which could look like below:
interface FooMixIn {
#JsonIgnore
String getBar();
}
Assume that your POJO looks like this:
class Foo {
private final String bar = null;
public String getBar() {
return bar;
}
#Override
public String toString() {
return bar;
}
}
Now you have to tell Jackson that you want to ignore this property:
String json = "{\"bar\":\"Value\"}";
System.out.println(json);
ObjectMapper deserializeMapper = new ObjectMapper();
deserializeMapper.addMixInAnnotations(Foo.class, FooMixIn.class);
System.out.println(deserializeMapper.readValue(json, Foo.class));
Above example prints:
{"bar":"Value"}
null
Without deserializeMapper.addMixInAnnotations(Foo.class, FooMixIn.class); line above program prints:
{"bar":"Value"}
Value
EDIT 1
If you want to achieve result like you showed you have to create two ObjectMappers and customize them. See below example:
String json = "{\"bar\":\"Value\"}";
ObjectMapper deserializerMapper = new ObjectMapper();
Foo foo = deserializerMapper.readValue(json, Foo.class);
System.out.println("Foo object: " + foo);
ObjectMapper serializerMapper = new ObjectMapper();
serializerMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
serializerMapper.addMixInAnnotations(Foo.class, FooMixIn.class);
System.out.println("JSON: " + serializerMapper.writeValueAsString(foo));
For serialization you have to use one instance and for deserialization you have to use another instance.
Starting with Jackson 2.6, a property can be marked as read- or write-only. It's simpler than hacking the annotations on both accessors (for non-final fields) and keeps all the information in one place. It's important to note that a final field is considered writable by default by Jackson.
However, it's not enough for a final field to allow deserialization, because you can't have a setter on that field: it needs to be set via the constructor, either directly or using a builder or another type that can be deserialized by Jackson. When using the constructor with the properties as parameters, you need to specify which parameter corresponds to which property, using #JsonProperty:
public class Foo {
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private final String bar;
public Foo(#JsonProperty("bar") String bar) {
this.bar = bar;
}
public String getBar() {
return prop;
}
}

Jackson Data-Binding with Heterogeneous Json Object

I'm calling a rest service that returns a json object. I'm trying to deserialize the responses to my Java Beans using Jackson and data-binding.
The example Json is something like this:
{
detail1: { property1:value1, property2:value2},
detail2: { property1:value1, property2:value2},
otherObject: {prop3:value1, prop4:[val1, val2, val3]}
}
Essentially, detail1 and detail2 are of the same structure, and thus can be represented by a single class type, whereas OtherObject is of another type.
Currently, I've set up my classes as follows (this is the structure I would prefer):
class ServiceResponse {
private Map<String, Detail> detailMap;
private OtherObject otherObject;
// getters and setters
}
class Detail {
private String property1;
private String property2;
// getters and setters
}
class OtherObject {
private String prop3;
private List<String> prop4;
// getters and setters
}
Then, just do:
String response = <call service and get json response>
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(response, ServiceResponse.class)
The problem is I'm getting lost reading through the documentation about how to configure the mappings and annotations correctly to get the structure that I want. I'd like detail1, detail2 to create Detail classes, and otherObject to create an OtherObject class.
However, I also want the detail classes to be stored in a map, so that they can be easily distinguished and retrieved, and also the fact that the service in in the future will return detail3, detail4, etc. (i.e., the Map in ServiceResponse would look like
"{detail1:Detail object, detail2:Detail object, ...}).
How should these classes be annotated? Or, perhaps there's a better way to structure my classes to fit this JSON model? Appreciate any help.
Simply use #JsonAnySetter on a 2-args method in ServiceResponse, like so:
#JsonAnySetter
public void anySet(String key, Detail value) {
detailMap.put(key, value);
}
Mind you that you can only have one "property" with #JsonAnySetter as it's a fallback for unknown properties. Note that the javadocs of JsonAnySetter is incorrect, as it states that it should be applied to 1-arg methods; you can always open a minor bug in Jackson ;)

Snakeyaml load to JavaBean and if property is not found place in map

I would like to know if it is possible to have snakeyaml load a yaml document into a javabean and if it is unable to find a match for the entry in the document as a javabean property it will place it into a generic map within the javabean...
Ex.
public class Person {
private String firstName;
private String lastName;
private Map<String, Object> anythingElse;
//Getters and setters...
}
If I load a document that looks like:
firstName: joe
lastName: smith
age: 30
Since age is not a property in the bean I would like {age, 30} to be added to the anythingElse map.
Possible?
Thanks.
No it wouldn't be possible.
From my experience and attempts it doesn't work. If you would want to load a file into a object than all attributes in that objectclass would have to have a getter and setter (I.E. the class have to be JavaBean, see Wikipedia).
I used your Person Class (See the wiki page for a proper JavaBeanClass) and this code: http://codepaste.net/dbtzqb
My error message was: "Line 3, column 4: Unable to find property 'age' on class: Person" thus proving that this simple program cannot have "unexpected" attributes. This is my fast and easy conclusion. I've not tried extensively so it may be possible but I don't know such a way (you'll have to bypass the readingmethods and JavaBean). I've used YamlBeans (https://code.google.com/p/yamlbeans/) so it's a little different but I find it easier and working ;]
Hope it's helping!
Edit
Sorry for bumping this, better late than never! I didn't see the postdate until after I posted my answer.. But hopefully it would help others seeking help assweel :3
I haven't tried the following (semi-kludgy hack) using SnakeYaml, but I have it working using YamlBeans:
Basically the idea is to define a class that extends one of the concrete implementations of java.util.Map. Then define getters that pick out distinct values and a general getter that returns everything else:
public class Person extends HashMap<String, Object>
{
public String getFirstName()
{
return (String)this.get("firstName");
}
public String getLastName()
{
return (String)this.get("lastName");
}
public Map<String, Object> getExtensions()
{
Map<String, Object> retVal = (Map<String, Object>)this.clone();
retVal.remove("firstName");
retVal.remove("lastName");
return retVal;
}
}
I'm not sure how either SnakeYaml or YamlBeans prioritizes the different type information you see when introspecting on this class, but YamlBeans (at least) is content to deserialize info into this class as if it were any other Map and doesn't seem to get confused by the addition getters (i.e. doesn't trip up on the "getExtensions").
It is possible:
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.representer.Representer;
public class YamlReader {
public static <T> T readYaml(InputStream is, Class<T> clazz){
Representer representer = new Representer();
// Set null for missing values in the yaml
representer.getPropertyUtils().setSkipMissingProperties(true);
Yaml yaml = new Yaml(representer);
T data = yaml.loadAs(is, clazz);
return data;
}
}

Xstream: Implicitly ignoring all fields

How do I tell Xstream to serialize only fields which are annotated explicitly and ignore the rest?
I am trying to serialize a hibernate persistent object and all proxy related fields get serialized which I don’t want in my xml.
e.g.
<createdBy class="com..domain.Users " reference="../../values/createdBy"/>
is not something I want in my xml.
Edit: I don’t think I made this question clear. A class may inherit from a base class on which I have no control (as in hibernate’s case) on the base class properties.
public class A {
private String ShouldNotBeSerialized;
}
public class B extends A {
#XStreamAlias("1")
private String ThisShouldbeSerialized;
}
In this case when I serialize class B, the base class field ShouldNotBeSerialized will also get serialized. This is not something I want. In most circumstances I will not have control on class A.
Therefore I want to omit all fields by default and serialize only fields for which I explicitly specify the annotation. I want to avoid what GaryF is doing, where I need to explicitly specify the fields I need to omit.
You can omit fields with the #XstreamOmitField annotation. Straight from the manual:
#XStreamAlias("message")
class RendezvousMessage {
#XStreamOmitField
private int messageType;
#XStreamImplicit(itemFieldName="part")
private List<String> content;
#XStreamConverter(SingleValueCalendarConverter.class)
private Calendar created = new GregorianCalendar();
public RendezvousMessage(int messageType, String... content) {
this.messageType = messageType;
this.content = Arrays.asList(content);
}
}
I can take no credit for this answer, just sharing what I have found. You can override the wrapMapper method of the XStream class to achieve what you need.
This link explains in detail: http://pvoss.wordpress.com/2009/01/08/xstream/
Here is the code you need if you don't want the explanation:
// Setup XStream object so that it ignores any undefined tags
XStream xstream = new XStream() {
#Override
protected MapperWrapper wrapMapper(MapperWrapper next) {
return new MapperWrapper(next) {
#Override
public boolean shouldSerializeMember(Class definedIn,
String fieldName) {
if (definedIn == Object.class) {
return false;
}
return super
.shouldSerializeMember(definedIn, fieldName);
}
};
}
};
You might want to do all your testing before you implement this code because the exceptions thrown by the default XStream object are useful for finding spelling mistakes.
There was already a ticket for the XStream people:
Again, this is by design. XStream is a serialization tool, not a data
binding tool. It is made to serialize Java objects to XML and back. It
will write anything into XML that is necessary to recreate an equal
object graph. The generated XML can be tweaked to some extend by
configuration for convenience, but this is already an add-on. What you
like to do can be done by implementing a custom mapper, but that's a
question for the user's list and cannot be handled here.
http://jira.codehaus.org/browse/XSTR-569
I guess the only direct way is to dive into writing a MapperWrapper and exclude all fields you have not annotated. Sounds like a feature request for XStream.

Categories