Skip to content
This repository was archived by the owner on Nov 27, 2018. It is now read-only.

Case classes and nulls /2 #12

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open

Case classes and nulls /2 #12

wants to merge 7 commits into from

Conversation

cunei
Copy link

@cunei cunei commented Mar 19, 2013

Hello Will,

Thank you for considering my pull request. The binary compatibility requirement is a good point; I am therefore submitting a modified version of the patch, which includes a simple optional configuration mechanism. The standard JacksMapper works exactly as it did before. However, it is now also possible to write:

import com.lambdaworks.jacks._
import JacksOption._

val m=JacksMapper.withOptions(CheckCaseClassNulls(true))
import m.{writeValueAsString,readValue}

Concerning its usefulness: in the common case in which a group of case classes are recursively deserialized from a hand-written JSON configuration file, for example, it is possible that the person writing the configuration file forgets one or more of the fields. In the absence of a library-supported option to automatically detect nulls, it is then upon the library client code to manually check that every individual field of every generated case class is not null. That, of course, adds a great deal of complexity to the user code (and makes it in turn error-prone).

I hope you will consider this pull request, or a variant, for inclusion (at least, it is crucial in my case). Thanks!

Antonio Cunei added 7 commits March 19, 2013 15:23
If a property is missing, and no default value exists,
it should stop and complain rather than using nulls to
build a case class instance.
However, the constructor can be made private.
For clarity, as further "CaseClass" options are arriving.
The version 2.1.4 of Jacks deserializes a missing property corresponding
to an Option case class field as "null", rather than None. This leads to
situations like:

Start with ""
Deserialize -> Z(null)
Serialize -> """{"s":null}"""
Deserialize -> Z(None)

Which may be undesirable. When the option CaseClassCheckNulls is in use,
nulls should never be generated; therefore, deserialize missing properties
that correspond to Option fields as None instead, unless an explicit
default is specified.
Usually the annotation @JsonInclude(Include.NON_NULL) will cause null fields not to be emitted. However,
in the case of serialization of an Option[] field containing None, the standard JacksMapper will still write "null":
since the field itself is not null, even when the flag is used None is serialized as null, regardless.

This behavior cannot be changed because of compatibility reasons. Hence, this option "CaseClassSkipNulls" is introduced,
with behavior complementary to CaseClassCheckNulls. When this option is selected, each Option field that contains None will
be skipped during serialization, rather than being serialized as null. This option does not override "ALWAYS" or other
Jackson flags explicitly set on classes or individual fields: if you do, the flags will still be honored.
Do skip null and None even when a default exists, in case
the default happens to be null or None
@diegovar
Copy link

diegovar commented Apr 3, 2013

I second this, this is Scala and usually nulls should be avoided like the plague. I find 2 behaviors of Jacks to be a bit disconcerting:

  1. If I have
case class Person(name: String)

and I parse "{}" I get a Person(null). IMO this should fail instead of returning this Person object, whereas parsing "{name: null}" should not. Parsing should not fail if the field is an Option (map absent fields to None) or it has a default value (use the default one, that is correct today).

  1. If I have
case class Person(name: String, carModel: Option[String], carPlate: String)

and I have an instance Person("John", None, null), I'd like it to be be able to serialize this as {"name": "John"}, ommitting the Nones and nulls, which I think are superflows and add only noise.

@diegovar
Copy link

diegovar commented Apr 3, 2013

Another argument in favor of this is that many people like us are probably migrating from Jerkson to Jacks, and thus this behavior would conflict with what Jerkson does with nulls and Nones. Jerkson did provide an option to explicitly serialize nulls, which was off by default.

@wg
Copy link
Owner

wg commented Apr 3, 2013

@diegovar thanks for your comments! I find it interesting that in (1) you want parsing to fail when a null property is omitted, and in (2) you want generation to omit properties that are null (carPlate). Aren't those mutually exclusive?

I've seen JSON that does both, some omits the property entirely when null, others include an explicit null, so I tend to think both cases should have the same behavior.

IMHO the correct way to treat optional JSON fields that may be omitted in Scala is to use default values, and not have a special case for nulls.

@cunei has a good point regarding checking for omitted fields as part of validation, but there's a lot more to validation than that and using existing code like scalaz validation seems more appropriate than trying to make jacks do validation.

As far as omitting nulls in JSON output, jacks supports Jackson's JsonInclude annotation at both class and field levels, so you can use Include.NON_NULL to skip nulls and Include.NON_EMPTY to skip Nones, empty collections, etc.

@diegovar
Copy link

diegovar commented Apr 4, 2013

On 1, I don't think they're mutually exclusive. It's how Jerkson worked, and how a library like Json4S works as well. Truth is, for the JSON blob you generate, writing a null is IMO equivalent to ommitting the property, only nastier. In most cases I believe you want to omit it, and in the cases where you want it you should be able to force it to appear always.

I encourage you to look at how Json4S works in these cases, I believe theirs is the most reasonable approach for most cases.

Diego

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants