Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CoercionConfig[s] mechanism for configuring allowed coercions #2113

Closed
cowtowncoder opened this issue Aug 16, 2018 · 4 comments
Closed
Labels
most-wanted Tag to indicate that there is heavy user +1'ing action
Milestone

Comments

@cowtowncoder
Copy link
Member

One of recurring themes regarding handling of simple scalar values (like numbers, Enums) is that some users want strict handling (only accept values with exactly matching types; JSON boolean for Java boolean), whereas others prefer coercions that may accept "close enough" matches (for example, accepting number 0 to map as false for Java boolean property).

Right now configurability is both limited and global: main mechanism is MapperFeature.ALLOW_COERCION_OF_SCALARS; and there are DeserializationFeatures like FAIL_ON_NULL_FOR_PRIMITIVES, FAIL_ON_NUMBERS_FOR_ENUMS.

But we could and should make use of another existing configuration mechanism:
@JsonFormat has property lenient, which, if set to false, could indicate strict handling.

This setting is actually already used for Date/Time values, but as format feature it is already accessible, and can be configured:

  1. For individual property
  2. For type (class)

thus allowing much more fine-grained setting.

We could and should start with:

  1. Booleans
  2. Numbers (int, long, double, float etc)
  3. Enums

Note, too, that since default for property is OptBoolean.DEFAULT, we can even distinguish between "lenient" / "not lenient" / "default", to allow further coercions, and leaving current set as "default" choice.

@cowtowncoder
Copy link
Member Author

May need figure out what to try to get done for 2.10, considering that there is now also:

  • MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES / JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_VALUES which could be allowed (for example) if accepting String for Boolean -- and then True or TRUE would be acceptable.

Also: need to figure out precedence of ALLOW_COERCION_OF_SCALARS vs leniency. Seems like order should be something like (starting from lowest precedence up):

1 a Global leniency: baseline
1 b ALLOW_COERCION_OF_SCALARS
2. Per-type leniency
3. Per-property leniency

where both 1a and 1b need to be enable to allow coercion.

cowtowncoder added a commit that referenced this issue Sep 10, 2019
@cowtowncoder cowtowncoder added 2.12 and removed 2.10 labels Apr 12, 2020
@cowtowncoder
Copy link
Member Author

I actually have bit better idea now; something like CoercionConfig (for DeserializationConfig), modeled after ConfigOverrides. It would be sort of parallel and leave @JsonFormat.lenient to focus on textual-only aspects (for primary representation); focus here would strictly be for coercion from secondary shapes.

@cowtowncoder cowtowncoder added this to the 2.12.0 milestone Jun 8, 2020
@cowtowncoder cowtowncoder changed the title Make use of @JsonFormat.lenient to force strict (or not) handling of scalars Add CoercionConfig[s] mechanism for configuring allowed coercions Jun 8, 2020
@cowtowncoder
Copy link
Member Author

I have now implemented system in which per-mapper CoercionConfig setup (configurable via MapperBuilder -- preferable -- or directly on ObjectMapper (for 2.x)) can be modified to add specific rules on potential coercions between target type (what is the expected type) and input shape (what kind of secondary value is seen), and action (CoercionAction).

Four CoercionAction types are defined:

  • Fail: coercion not allowed, throw exception
  • AsNull: allow, coerce to null (although may be further mapped via other mechanisms)
  • AsEmpty: allow, coerce to "empty" value of type (empty Collection, POJO with no properties set)
  • TryConvert: allow if there is logical conversion (for example String "123" can be parsed, converted to int value 123)

Input shapes are defined with CoercionInputShape enum which roughly corresponds to JsonToken values, but also has 3 logical types for "empty" String, Array and Object as special cases.

Target type is specific both by concrete (specific type), Class and new LogicalType that has a smaller set of values.

Rules can be targeted at 3 levels:

  1. For specific concrete type (Class), input shape
  2. For logical type (like LogicalType.Boolean) -- covers boolean, Boolean, elements in boolean[], AtomicBoolean -- and input shape
  3. Default action for coercions from input shape, used if no per-type (concrete or logical) specified -- most commonly used for input shape of EmptyString

This feature allows defining coercion rules like:

  • Let empty String value become POJO similar to being deserialized from { } JSON Input (especially useful for XML)
  • Let empty String value become null for specified type(s)
  • Prevent coercion from JSON Numbers into Java booleans (by default non-zero JSON Integers map to Boolean values as true)

cowtowncoder added a commit that referenced this issue Jun 8, 2020
… lots of work to make deserializer implementations use it.
@cowtowncoder
Copy link
Member Author

One additional note: the main configuration system has been implemented, as well as support in StdDeserializer, deserializers will need to be upgraded to take use of it -- some of jackson-databind has been retrofitted, but there will be a lot of work completing that over time, as well as upgrading datatype libraries.

There is also the question of deprecating of legacy settings (some MapperFeatures / DeserializationFeatures), removing from 3.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
most-wanted Tag to indicate that there is heavy user +1'ing action
Projects
None yet
Development

No branches or pull requests

1 participant