I am progressing with implementation of major features for Jackson 2.9:

https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.9

and finally got third one (out of tentative 11) implemented:

https://github.com/FasterXML/jackson-databind/issues/1399

What this allows is specifying that a property should be merged:
meaning that instead of always constructing a new value, assigning
values to it, recursively, an attempt is made to first check current
value (if there is a getter, or field to use for access), and if so,
update that value instead.
This has been on top-10 requested features list for years.

Feature is intended to replace general "deep merge" functionality, as
it both allows more granularity (not everything needs to be merged)
and turns out to be easier to implement.
It uses already existing secondary `deserialize()` method, formerly
only used via `mapper.updatingReader(existingValue)` for "shallow"
merge.
Jackson databind supports this for POJOs, Collections and Maps; for
other types, more work would be needed, esp. for 3rd party datatypes.

Current (*) API design allows for 3 levels of specifying whether
merging should be attempted:

1. Property annotation:

    static class Config {
        @JsonSetter(merge=OptBoolean.TRUE)
        public ServerSettings server = new ServerSettings(124, false);
    }

    which applies just for that particular property


2. Type defaulting

        ObjectMapper mapper = new ObjectMapper();
        mapper.configOverride(ServerSettings.class).setSetterInfo(
                JsonSetter.Value.forMerging());

    which is used as the default (in absence of per-property
definition) for all properties with matching declared type (here all
instances of `ServerSettings`)

3. Global baseline

      mapper.setDefaultSetterInfo(JsonSetter.Value.forMerging());

    which is used if neither per-property nor per-type settings are used.
    This may be useful for some configuration use cases.

-----

Ok, so far so good. This works fine for regular properties. But there
are a few issues that may come up, and I have been trying to figure
out proper way to deal with. In most cases the general issue is that
certain values are simple not mergeable at all, and the question is
whether this should result in:

(a) Default, non-merging functionality to be used, or
(b) Signaling an error to user via InvalidDefinitionException

specific cases I can think of include:

1. Property has `null` value
2. No accessor (getter/field)
3. Property is passed as Creator property
4. Property value is immutable and either can not be merged at all
(like Integer), or would require elaborate rules (array values)
5. Non-core types via modules may not (yet) support updating
6. Property value requires polymorphic handling, and incoming type may
differ from existing value, possibly not mergeableOf these, null
handling probably should just proceed with regular handling and not
signal error (although it'd be easy enough to add a `MapperFeature` to
change this globally).

Of these, the last one seems trickiest. But let's go over these in order.

Not having accessor would seem like something to ignore and proceed
with default handling, although as with `null`s maybe someone
somewhere might want signaling.

Creator property may be something that can be supported, I'll have to
think about that.

Not-mergeable thing is something for which I added

`MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE`

for, defalting to `true` so that regular deserialization proceeds,

As to non-core types... well, support would need to be added. My main
concern is just that ideally there would be a way to signal cases that
ought to allow merge from others that do not, but this is somewhat
difficult to implement. One possibility would be to actually make
`IGNORE_MERGE_FOR_UNMERGEABLE` default to false, and rely on
`StdScalarDeserializer` and `StringDeserializer` to avoid exception
(as primitives/wrappers, Strings, are immutable), but let other types
throw exception by default

I think all of above can be handled in a way that should work well
enough. But the really tricky case might be the polymorphic one.

Challenge here is two-fold:

1. Handling of polymorphic wrapper is tricky, as there is not "update
existing value" variant for `deserializeWithType()` -- one would
probably need to be added
2. If handling is added, what should be done in likely case of
existing type not being assign-compatible with type from JSON? Perhaps
assignment-compatibility would be required, such that existing value
to update must be same type or super-type, and if not, exception
thrown; either automatically, or wait for assignment incompatibility?

So it perhaps should be possible to support this use case, with
limitations that types (default to update, indicted type from data)
must be compatible enough for operation to make sense.

Anyway -- I would really appreciate suggestions, feedback here.

-+ Tatu +-

----

Aside from how to handle problem cases, there's the question of how to
configure said handling. There are at least 2 approaches:

1. Addition of MapperFeatures (since these are not configurable on
per-call basis), and possible matching JsonFormat.FormatFeature for
per-property annotation
2. Changing `@JsonSetter.merge` to be dedicated Enum with more options
than just true/false/default.

I don't have preference over choices, but going with (2) would be much
more difficult to do after 2.9 is released, whereas (1) can be added
at any point.

-+ Tatu +-


(*) current meaning that it is subject to changes for now, esp. based
on feedback, improvement ideas.

-- 
You received this message because you are subscribed to the Google Groups 
"jackson-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to