-
-
Notifications
You must be signed in to change notification settings - Fork 175
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
Default values for primitive parameters #26
Comments
@michaelhixson Value defaulting like this is not supported; there is no mechanism to do that at this point on Personally I think value defaulting is a bad idea with data formats, but in this case it looks like language itself is to provide defaulting, which is more sensible. Things just get tricky with respect to division of responsibilities; if there is a way for deserializer to provide something to indicate runtime to populate values maybe that could work. I don't know Kotlin runtime well enough to know if or how that could be done. |
@cowtowncoder Thanks. So I'm looking into how one might accomplish this. I'm not familiar with Kotlin either, but it appears that I can:
Is there a place in Jackson, wherever it deals with beans, where it's juuuust about to use reflection to invoke a Java constructor with a certain set of parameters, and I can intercept that and do something else? And if so, would I be able to tell apart parameters whose values came from the JSON source from parameters who didn't appear in the source? |
@michaelhixson Abstraction you might want to use is |
@cowtowncoder It doesn't seem possible to achieve what I want, but a small change to I'd be willing to write the pull requests for My code that almost works (see the class KotlinMod : SimpleModule() {
override fun setupModule(context: SetupContext) {
context.addValueInstantiators { conf, desc, instantiator ->
if (instantiator is StdValueInstantiator) {
KotlinValueInstantiator(instantiator)
} else {
instantiator
}
}
}
}
class KotlinValueInstantiator(src: StdValueInstantiator): StdValueInstantiator(src) {
override fun createFromObjectWith(ctxt: DeserializationContext,
args: Array<out Any>)
: Any? {
val creator = withArgsCreator
val kotlinFunction = when (creator) {
is AnnotatedConstructor -> creator.annotated.kotlinFunction
is AnnotatedMethod -> creator.annotated.kotlinFunction
else -> null
} ?: return super.createFromObjectWith(ctxt, args)
val wasSeen: (KParameter) -> Boolean = {
//
// FIXME: This is wrong.
//
// This is not distinguishing between "the parameter was seen
// and its value was zero" and "the parameter was not seen".
//
// The information I want lives in PropertyValueBuffer, in the
// _paramsSeen and _paramsSeenBig fields. The
// PropertyBasedCreator had that information available (by way
// of the buffer) when it called this method.
//
// Could it pass that information along somehow?
//
args[it.index] != 0
}
return kotlinFunction.callBy(kotlinFunction.parameters
.filter { !it.isOptional || wasSeen(it) }
.associateBy({ it }, { args[it.index] }))
}
} Proposal
package com.fasterxml.jackson.databind.deser;
public interface ParametersSeen {
boolean wasParameterSeen(int index);
}
@Override
public boolean wasParameterSeen(int index) {
if (index < 0 || index >= _creatorParameters.length) {
return false;
}
if (_paramsSeenBig == null) {
int mask = _paramsSeen >> index;
return (mask & 1) == 1;
} else {
return _paramsSeenBig.get(index);
}
}
public Object createFromObjectWith(DeserializationContext ctxt,
Object[] args,
ParametersSeen seen) throws IOException {
return createFromObjectWith(ctxt, args);
}
public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException
{
Object bean = _valueInstantiator.createFromObjectWith(ctxt,
buffer.getParameters(_allProperties),
buffer);
class KotlinValueInstantiator(src: StdValueInstantiator): StdValueInstantiator(src) {
override fun createFromObjectWith(ctxt: DeserializationContext,
args: Array<out Any>,
seen: ParametersSeen)
: Any? {
val creator = withArgsCreator
val kotlinFunction = when (creator) {
is AnnotatedConstructor -> creator.annotated.kotlinFunction
is AnnotatedMethod -> creator.annotated.kotlinFunction
else -> null
} ?: return super.createFromObjectWith(ctxt, args, seen)
return kotlinFunction.callBy(kotlinFunction.parameters
.filter { !it.isOptional || seen.wasParameterSeen(it.index) }
.associateBy({ it }, { args[it.index] }))
}
} |
@michaelhixson No immediate objections, although I'll need to have a closer look. The main concern as usual is backwards compatibility... and addition of a new argument to |
Extending on my earlier comment: there would really be a good upgrade story on how existing code would be kept working with new method, while allowing use of the new method. Since Another question concerns naming of Anyway, I think this approach could well work and be useful, so looking forward to a PR? |
Exploring alternatives... In my use case, in my first proposal, Jackson is doing some unwanted work. It is eagerly finding default values for all missing parameters regardless of whether I need them. Ideally it would find those values lazily and individually upon request. Proposal 2
// true if the parameter was seen in the source.
public boolean hasParameter(SettableBeanProperty prop)
{
if (_paramsSeenBig == null) {
return ((_paramsSeen >> prop.getCreatorIndex()) & 1) == 1;
} else {
return _paramsSeenBig.get(prop.getCreatorIndex());
}
}
// The value of the parameter if seen, else fill in a default.
// Like a single-argument version of the existing getParameters method.
public Object getParameter(SettableBeanProperty prop)
throws JsonMappingException
{
Object value;
if (hasParameter(prop)) {
value = _creatorParameters[prop.getCreatorIndex()];
} else {
value = _creatorParameters[prop.getCreatorIndex()] = _findMissing(prop);
}
if (_context.isEnabled(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES) && value == null) {
throw _context.mappingException(
"Null value for creator property '%s'; DeserializationFeature.FAIL_ON_NULL_FOR_CREATOR_PARAMETERS enabled",
prop.getName(), prop.getCreatorIndex());
}
return value;
}
// Delegates to the existing createFromObjectWith method for backwards compatibility.
// Essentially does what the first line of PropertyBasedCreator.build(...) used to do.
public Object createFromObjectWith(DeserializationContext ctxt,
SettableBeanProperty[] props, PropertyValueBuffer buffer)
throws IOException
{
return createFromObjectWith(ctxt, buffer.getParameters(props));
}
public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException
{
// Instead of immediately calling buffer.getParameters(_allProperties), let the value
// instantiator decide what to do. That way, applications can override the behavior.
// The default implementation is equal to the old behavior.
Object bean = _valueInstantiator.createFromObjectWith(ctxt,
_allProperties, buffer);
class KotlinValueInstantiator(src: StdValueInstantiator): StdValueInstantiator(src) {
override fun createFromObjectWith(ctxt: DeserializationContext,
props: Array<out SettableBeanProperty>,
buffer: PropertyValueBuffer): Any? {
val creator = withArgsCreator;
val kotlinFunction = when (creator) {
is AnnotatedConstructor -> creator.annotated.kotlinFunction
is AnnotatedMethod -> creator.annotated.kotlinFunction
else -> null
} ?: return super.createFromObjectWith(ctxt, props, buffer)
return kotlinFunction.callBy(kotlinFunction.parameters
.filter { !it.isOptional or buffer.hasParameter(props[it.index]) }
.associate { it to buffer.getParameter(props[it.index]) })
}
} |
@cowtowncoder Let me know if you would you prefer that I phrase my idea as an incomplete pull request (without javadoc, tests, or meaningful commit messages) instead of writing it here as I have been. |
@michaelhixson Yes I think that makes sense. From what I can see I think suggestion makes sense, but it is easier to see pieces fit together as part of PR. Thank you for working on this and improving handling. As I mentioned earlier I think this will be useful for Scala as well. |
@michaelhixson If you're working for a fix on this, could you also please look into #29 ? I believe it's almost the same issue, except for non-primitive values (e.g. Jackson deserializes missing property as null when it could have used the default value provided in @JsonCreator). It would be great if your work could resolve both issues. |
@knes1 It is the same issue. I worded this issue poorly; it's not only about primitive types. The changes discussed above should work for reference types as well. |
@cowtowncoder Thanks for merging that PR. I have questions about dependency versions... Right now, If I bump the dependency versions to 2.8.0-SNAPSHOT, some of the existing tests fail for me: |
@michaelhixson You should bump dependencies to 2.8.0-SNAPSHOT, so that we can work through all the issues there may be. I did not realize that Kotlin hadn't yet bumped master to 2.8.0-SNAPSHOT. But first I'll make sure there is As to enums, I haven't been aware, but maybe @apatrida might be? |
Ok created 2.7 branch, updated version of master. Left main dependencies as is for now so as not to break build before someone has a chance to see what's up with test failures. |
I figured out a fix for the enums. If I change this line: jackson-module-kotlin/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt Line 70 in a3985d0
from this: if (member is AnnotatedConstructor) { to this: if (member is AnnotatedConstructor && !member.declaringClass.isEnum) { then the tests all pass again in 2.8.0-SNAPSHOT. Edit: Made a PR for this #31 |
I can review the PR... |
I'm working on this now, the default values including those from #29 |
Fixed, committing soon. |
Awesome, thanks! Good idea on |
@michaelhixson I think there is a Jackson feature switch I should be checking (force accessibility) but in Kotlin there are so many things that could be unaccessible it'd pretty much force people to turn it on anyway. From the prospective of the caller to Jackson it is accessible, from the perspective of Jackson it isn't. I wonder if there is anyway I can check accessible from viewpoint of another class (the caller) and then set it automatically. not sure is possible, but for now just forcing accessibility. |
FWTW, the feature (enabled by default) is typically only meant to be disabled on security-limited platforms like Applets or (maybe) Google AppEngine. So for general usage it is most likely enabled. There is also related |
I don't see those flags really being used by StdValueInstantiator or things it uses (AnnotatedConstructor for example which just calls ClassUtil.checkAndFixAccess if not accessible without checking the feature) .. But I added them to the Kotlin module, will commit in a moment. |
For the paths do not short circuit and just call the super class method for creating object:
|
…S` and `OVERRIDE_PUBLIC_ACCESS_MODIFIERS` are respected when call-by-parameter is used.
I was surprised by the behavior of Jackson+Kotlin with respect to primitive parameters that have default values. It seems like those default values are ignored, and zero is used instead.
Here's an example:
That prints:
But I wanted it to print:
Is that beyond the scope of this module? Is it impossible for some reason?
The text was updated successfully, but these errors were encountered: