Jackson deserialization: How to get a default value even if the JSON property was null - jackson

In my project I'm using Jersey 2.23.1 with Jackson for JSON support.
When I'm getting a request with something like { "foo":null, "bar":"123" } as JSON, matching with class A{String foo; String bar;} Jersey first creates and instance of A (with default values if specified in constructor), then deserialize JSON to a temporary object A', then copies all JSON fields that were specified in JSON from A' to A. If I have default values in A-class constructor, and have fields equal to null in JSON, all my default values are erased and replaced by null. So in the example above, if I have a default value for the foo field, it will be replaced by null in the object Jersey will return as param for my #Path annotated method.
I'm using #JsonInclude(Include.NON_NULL) on A class to avoid the transfer of null fields during Response. But it only works for serialization, what about deserialization? I mean, when having { "foo":null } as JSON results in field "foo" = null in new object instance after deserialization.
Here is some code to sum all of this :
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(value = Include.NON_NULL)
public class User {
public enum EUserRole {
PARENT, STUDENT, PROF, ADMIN
}
#Id
public String id;
public String firstName;
public String lastName;
public EUserRole role;
public User() {
id = ObjectId.get().toString();
role = EUserRole.STUDENT;
lastName = "RandomLastName";
}
}
if I'm passing this kind of JSON
{
"id":null,
"lastName":null,
"firstName":"Random First Name",
"role":"STUDENT"
}
to my method (in controller)
#POST
public Response createUser(final User entity) {
}
it results that all null fields in JSON are set to null in my entity and not set to the constructor default values.
Do you know if there is a way to specify Jackson to ignore null fields during deserialization? Or is this a Jersey-related behavior?

There is no way to ignore data from JSON payload in that sense, based on value contained (you can use ignoral to just ignore all values for given property).
So if you want to avoid null assignment, you need define a setter that will just swallow null value (that is, only assign non-null).
Ability to prevent null assignment might a useful feature to add via #JsonFormat.Feature, something like:
// hypothetical no such feature exists yes
#JsonFormat(without = JsonFormat.Feature.ALLOW_NULL_ASSIGNMENT)
so perhaps this could be a feature request.
And the reason I think this belongs to per-property handling is that this seems like a specific rule for some of the properties. Although perhaps there could also be a matching global setting if it seems users really like such null-ignoral.

Related

Providing Jackson Mapper multiple ways to deserialize the same object

I'm trying to deserialize two types of json:
{
name: "bob",
worksAt: {
name: "Bobs department store",
location: "downtown"
},
age: 46
}
and
{
name: "Tom",
worksAt: "company:Bobs department store",
age: 27
}
into these objects:
The first way creates two new objects, the second way requests the object from the database based on the contents of a string.
sort of like how jackson mapper can deserialize an arbitrary string into an object, for objects like this:
public class Company{
public String name;
public Employee[] employees
public Company(){}
public Company(String json){
//turn string into object using whatever encoding you want blah blah blah...
}
}
The trouble is I need both. I need it to handle objects and strings. Both could arrive from the same input.
The first think I tried was making a Converter
It says these create a delegate type to pass to the deserializer, but the converter is always applied even when the datatype isn't a string. So that didn't work.
I've also tried a normal deserializer, but I can't find a way to defer to the BeanDeserializer. The beanDeserializer is so complicated that I can't manually instantiate it. I also see no way to defer to a default deserializer in jackson mapper.
Do I have to re-implement jackson mappers deserialization to do this? Is there any way for a deserializer to say "I can't do this, use the default implementation."?
Edit: Some further progress. Based on the Jackson Mapper source code, it looks like you can instatiate bean deserializers like this:
DeserializationConfig config = ctxt.getConfig();
JavaType type = config.constructType(_valueClass);
BeanDescription introspect = config.introspect(type);
JsonDeserializer<Object> beanDeserializer = ctxt.getFactory().createBeanDeserializer(ctxt, type , introspect);
but for some reason all the _beanProperties have the FailingDeserializer set for their _valueDeserializer and the whole thing fails. So I have no idea why that happens...
Have you tried writing a custom deserializer? This gives you the most control on how Jackson deserializes the object. You may be able to try to deserialize one way, and if there's an error, try another way.
Jackson can also handle polymorphic deserialization, though this would require a small change to the json to include type information, and it sounds like your problem constraints might not allow that.
If I understand the problem correctly, I would recommend using JsonNode. You can define a setter in your top-level type like this:
setWorksAt(JsonNode node) {
if (node.getNodeType == JsonNodeType.STRING) {
String name = node.getText();
name = name.substring(name.lastIndexOf(':'));
this.company = new Company(name);
} else if (node.getNodeType == JsonNodeType.OBJECT) {
this.company = mapper.treeToValue(node, Company.class);
}
}
That allows you to handle the two separate worksFor inputs, while still allowing the standard mapper to handle any substructures for the OBJECT case.
With recent versions of Jackson (2.8+ I think, definitely works with 2.9) you can use multiple #JsonCreator and do something like this:
public class Company {
private String name;
private String location;
private Company(String name, String location) {
this.name = name;
this.location = location;
}
private Company(String stringRepresentation) {
// add code here to parse string and extract name and location
}
#JsonCreator
private static Company fromJson(
#JsonProperty("name") String name,
#JsonProperty("location") String location)
{
return new Company(name, location);
}
#JsonCreator
private static Company fromJson(String str) {
return Company(str);
}
}

What is the recommended way to do partial updates with PATCH in ServiceStack?

I am building a RESTful API using the ServiceStack framework. A lot of the resources that I need to update are quite big, with up to 40 attributes per class, so I would like to do partial updates instead of replacing the entire resource. Often the client will only need to update one or two attributes out of the 40, so I would like to just send a JSON body consisting of the few attributes.
Since all combinations of attributes are possible, it is not feasible to make an "Update" class per class as suggested here: https://github.com/ServiceStack/ServiceStack/wiki/New-Api#patch-request-example
In the Microsoft ASP.NET WebAPI OData package there is a Delta class that takes a subset of a class and updates the resource based on this subset (http://www.strathweb.com/2013/01/easy-asp-net-web-api-resource-updates-with-delta/). This is the functionality I would like to have, as I will be having quite a few classes so a generic method would be best.
Basically, if I have a class
public class MyClass {
public int a { get; set; }
public int b { get; set; }
...
public int z { get; set; }
}
I would like to update a resource of MyClass with a PATCH request with body
{"a":42,"c":42}
Is there a standard or recommended way to accomplish this with ServiceStack?
Declare any scalar values in your DTO as nullable. This will allow you to determine which fields were actually sent in the request:
public class MyClass {
public int? a { get; set; }
public int? b { get; set; }
public int? c { get; set; }
// etc.
// object-type properties are already nullable of course
public string MyString { get; set; }
}
Now if a client sends a partial request, like so:
{ "a": 1, "b": 0 }
You'll be able to determine which properties were actually sent when inspecting your DTO:
myClass.a == 1
myClass.b == 0
myClass.c == null
myClass.MyString == null
etc.
Set up a PATCH route for your DTO and implement a Patch method in your service:
public object Patch(MyClass request)
{
var existing = GetMyClassObjectFromDatabase();
existing.PopulateWithNonDefaultValues(request);
SaveToDatabase(existing);
...
}
PopulateWithNonDefaultValues is key here. It will copy values from your request object onto the database entity, but will only copy properties that are not the default values. Thus, if a value is null, it won't copy it, because the client didn't send a value for it. Notice that it will copy an integer value of zero though, because we made it a nullable int, and the default value for a nullable int is considered by this method to be null, not zero. Declaring your DTO properties as nullable shouldn't cause much of a hassle in the rest of your code.
Note that this approach works easily with JSON. If you need to support XML requests/responses, you may need need to do some additional work with DataContract/DataMember attributes to insure that nulls are handled correctly.
While esker's response is fine I would like to add that it might not be enough for nullable fields - since you don't know if the deserializer or the user have created that null field.
One approach would be to peek at the raw request.
A different approach is to ask the user to provide additional request (querystring) parameter to clearly specify which fields to patch.
Something like: patch_fields=name,description,field3
The bonus of that approach is that the end user has more control over the patching and is not overriding a value by mistake (because he used the original entity and forgot to clear some fields)

How to change the format of an attribute in a CActiveRecord returned by a findAll method?

I have an attribute in my model that is stored in binary format inside database.
In case the attribute is a geometric( polygon ) object.
This object can be casted to several string representations.
So how can I attach an event after a find execution that allows me to change the attribute on returned set only?
My first guess was to use the onAfterFind event but it is not calling the handler with the created element as documentation suggests. My first attempt was the following in the controller.
// an activeRecord class
GeoTableBinaryData extends CActiveRecord {
... // normal active record with a table which has a binary attribute called geom
}
$model = GeoTableBinaryData::model();
$model->onAfterFind->add(
function( CEvent $evt ){
// get the finded object to update the geom attribute on the fly here want
// a text representation in other case would transform it to XML or JSON
}
);
foreach ( $model->findAll() as $geoInfo )
{
... // output serialized geometry
}
The correct way of doing this, is that in your model have a afterFind method like:
protected function afterFind()
{
$this->someAttribute = $this->methodToChangeTheAttribute($this->someAttribute);
return parent::afterFind();
}
and that's all, when you will use AR's methods, every found model will pass through afterFind() and alter the someAttribute as you want.
You can also write getters for your different formats:
public function getGeoAsString()
{
// Create the string from your DB value. For example:
return implode(',', json_decode($this->geom));
}
Then you can use the geoAsString like a regular (read-only) attribute. You can also add a setter method, if you want to make it writeable.

JEE6 Producer for NewCookie

I would like to create a CDI producer for
javax.ws.rs.core.NewCookie(java.lang.String name,
java.lang.String value,
java.lang.String path,
java.lang.String domain,
java.lang.String comment,
int maxAge,
boolean secure)
in such a way that the value will be different each time. I did some JEE6 a while ago but my memory is poor!
for ex. my producer for a simple logger is
#Produces
public Logger produceLogger(final InjectionPoint injectionPoint) {
final String injectingClass = injectionPoint.getMember().getDeclaringClass().getName();
logger.info("creating logger for : " + injectingClass);
return Logger.getLogger(injectingClass);
}
Any help appreciated
If you are able to calculate a unique value within a producer method without any additional parameters, then all you have to do is annotate a method with the return value NewCookie:
#Produces NewCookie createCookie() {
// create cookie and its value
}
If you need to create it subject to some external parameter, then this producer method can have parameters like any other method - but, all of these are injection points and must be obtainable by the container.
#Produces NewCookie createCookie(String value) {
// create cookie with parameter value
}
Now, a primitive type (as well as a String) has the problem, that you for sure have other instances of the same type with a different meaning, so you either use a special class like MyValue wrapping your String and use this as an injection point or annotate it with a custom annotation.
#Produces NewCookie createCookie(#CookieValue String value) {
// create cookie with parameter value
}
Then of course, you need again some place where this injected value is produced.
#Produces #CookieValue String createCookieValue() {
// create value
}
Check out the JavaEE 6 Tutorial or the CDI Spec for more information.

EclipseLink - #ReadTransformer

I have this code:
#Column(name = "foo")
#ReadTransformer(transformerClass=transformer.class)
private Date foo;
public static class transformer implements AttributeTransformer {
#Override
public void initialize(AbstractTransformationMapping atm) {
}
#Override
public Object buildAttributeValue(Record record, Object o, Session sn) {
}
}
My question is, how do I get the value to transform (from column foo) inside of buildAttributeVaule? It is not inside the record array.
You need one or more #WriteTransformer to write the fields you want selected (and thus get them selected), #Column is not used with a transformation mapping.
However, if you just have a single column, then just use a converter instead, #Convert,
http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Mapping/Basic_Mappings/Default_Conversions_and_Converters
First check that the SQL generated is reading in the "foo" column by turning on logging. If it is, then check that the database is returning "foo" and not "FOO" - java is case sensitive on string looksups. It could be that "FOO" is in the record instead of "foo".