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

Allow disabling Integer to String coercion via CoercionConfig #3013

Closed
emilkostadinov opened this issue Jan 12, 2021 · 20 comments
Closed

Allow disabling Integer to String coercion via CoercionConfig #3013

emilkostadinov opened this issue Jan 12, 2021 · 20 comments
Labels
coercion-config Issue related to 2.12 added "CoercionConfig"
Milestone

Comments

@emilkostadinov
Copy link

Describe the bug

I am experiencing issues with the new coercion settings that were released in v2.12.0. I would like to disable coercion between String and Integer.

Version information

v2.12.1

To Reproduce

I have class Example (using Lombok annotations):

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Example {
    private String type;
}

The ObjectMapper setup:

ObjectMapper mapper = new ObjectMapper();
mapper.coercionConfigFor(LogicalType.Textual)
        .setCoercion(CoercionInputShape.Integer, CoercionAction.Fail);

How I try to deserialize:

Example example = mapper.readValue("{\"type\": 123}", Example.class);

Expected behavior

I expect an exception to be thrown saying that Integer cannot be converted to String. But what happens is that example is successfully created with type set to "123" which means there was conversion.

Is there anything wrong I do (maybe with the coercion configuration)?

@emilkostadinov emilkostadinov added the to-evaluate Issue that has been received but not yet evaluated label Jan 12, 2021
@cowtowncoder cowtowncoder added coercion-config Issue related to 2.12 added "CoercionConfig" 2.13 labels Jan 16, 2021
@cowtowncoder
Copy link
Member

@e1-emilkostadinov Thank you for reporting this! Your usage of the feature looks correct so that is not the problem.

Up until now coercions from other scalar types to String have been automatically allowed but with the new coercion-config settings it should be possible to prevent this. This requires some work for StringDeserializer (and perhaps couple of other related deserializers) to add applicable checks.

So this is just a case of missing checks and should be implemented: I mark it for 2.13 as the work would most likely be down when starting to consider features to add in 2.13, but it could probably be backported in 2.12 as well if change seems safe enough.

@cowtowncoder cowtowncoder removed the to-evaluate Issue that has been received but not yet evaluated label Jan 16, 2021
@stephenvsp
Copy link

Is there an ETA for 2.13?

@cowtowncoder
Copy link
Member

@stephenvsp Not really; I would not expect it before maybe June-July 2021, ideally it'd be relatively small increase but it will have to compete with 3.0 development work.
I haven't had time to look into this feature (and probably won't any time soon), but I can help others with PRs more easily.

@jiamo
Copy link

jiamo commented Feb 9, 2021

After it success created.
We can only use (Integer) ((Object) map.get("type")) to use the map.

@WojciechLuczkow
Copy link

WojciechLuczkow commented Apr 23, 2021

I use following walk-around for 2.12:

Create custom deserializer

public class CoercionLessStringDeserializer extends StringDeserializer {

  @Override
  public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {

    List<JsonToken> forbiddenTypes =
        Arrays.asList(
            JsonToken.VALUE_NUMBER_INT,
            JsonToken.VALUE_NUMBER_FLOAT,
            JsonToken.VALUE_TRUE,
            JsonToken.VALUE_FALSE);

    if (forbiddenTypes.contains(p.getCurrentToken())) {
      String message =
          MessageFormat.format("Cannot coerce {0} to String value", p.getCurrentToken());
      throw MismatchedInputException.from(p, String.class, message);
    }
    return super.deserialize(p, ctxt);
  }
}

And use it with objectMapper

      SimpleModule deserializerModule = new SimpleModule();
      deserializerModule.addDeserializer(
          String.class, new CoercionLessStringDeserializer());
      mapper.registerModule(deserializerModule);

More advanced approach would be to use ctxt.findCoercionAction(logicalType(), handledType(), CoercionInputShape.?) for each possible JsonToken.

@cowtowncoder
Copy link
Member

Thank you @WojciechLuczkow for showing so anyone who needs a work-around now can use it.
(there are alternatives, such as specifically looking for just VALUE_STRING but above works).

@cowtowncoder cowtowncoder added 2.14 and removed 2.13 labels Jul 15, 2021
@cowtowncoder cowtowncoder changed the title Disable String to Integer coercion Disable Integer to String coercion Aug 12, 2021
@Soggywaters
Copy link

For those who want to use this on a field of a model, you can also use the @JsonDeserialize(using=CoercionLessStringDeserializer.class) with @WojciechLuczkow's solution. Thank you for the deserializer!

@cowtowncoder cowtowncoder added the pr-welcome Issue for which progress most likely if someone submits a Pull Request label Aug 2, 2022
@cowtowncoder
Copy link
Member

Thank you for sharing @Soggywaters! I also marked this with "pr-welcome" since I probably won't have time to work on this soon, but think it should be quite doable if anyone else wanted to have a go. And I can help getting PR ready and merged even if not having time to implement it myself.

@Soggywaters
Copy link

Hi @cowtowncoder, Thank you for all that you do for this project.

About this issue - I would love to help where I can but does it really make sense to add this as a feature? I think the solution above works well for the problem (creating a deserializer extended from the string deserializer and leveraging the @JsonDeserialize annotation). Unless, you're talking about a simple annotation, like @EnforceStringOnly, where it would just implement a deserializer like above?

@cowtowncoder
Copy link
Member

@Soggywaters it would not require any API addition but application of existing CoercionConfig functionality. This is a global (per ObjectMapper) setting, not new annotation.

I agree that a custom String deserializer works well too, and although in general writing proper fully feature deserialiazers is non-trivial, String type is simple enough (does not f.ex support polymorphism) that it's not very complicated.

Tomasito665 added a commit to Tomasito665/jackson-databind that referenced this issue Sep 29, 2022
@Tomasito665
Copy link
Contributor

Hi all, I gave it my best shot in this branch: 2.14...Tomasito665:disable-int-to-string-coercion.
I'll wait two days and open the PR next Saturday for it to count towards my Hacktoberfest score 🍻.

@WojciechLuczkow
Copy link

WojciechLuczkow commented Sep 29, 2022

@Tomasito665 There is some code regarding VALUE_NUMBER_INT, what about VALUE_NUMBER_FLOAT and VALUE_TRUE/FALSE? Also _nullProvider is a accessible through _findNullProvider field - do you need to pass it to _parseString?

@cowtowncoder
Copy link
Member

If we are specifically talking about Integer-to-String coercion, neither VALUE_NUMBER_FLOAT nor VALUE_TRUE/VALUE_FALSE handling should be affected. They are not integers in this context (from JSON they are lexically different).

@Tomasito665
Copy link
Contributor

Thanks for the feedback, both.

@WojciechLuczkow I am not sure whether I can use _findNullProvider. It takes a BeanProperty as an argument, which, as far as I can tell, is only available when ContextualSerializer and ContextualDeserializer context resolution occurs. Let me know if I am missing something.

@WojciechLuczkow
Copy link

@cowtowncoder Thats right - this issue is regarding Integer to String coercion, but as far I remember deserializer was also doing unexpected coercion for

mapper.coercionConfigFor(LogicalType.Textual)
        .setCoercion(CoercionInputShape.Float, CoercionAction.Fail);
mapper.readValue("{\"type\": 1.5}", Example.class);

and

mapper.coercionConfigFor(LogicalType.Textual)
        .setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail);

mapper.readValue("{\"type1\": true, \"type2\": false}", Example.class);

@Tomasito665 You are right, sorry for making a fuss :)
That is quite complicated that's why after spending some time trying to propose fix i ended up with work-around, which I posted above.

@cowtowncoder
Copy link
Member

cowtowncoder commented Sep 30, 2022

@WojciechLuczkow Fair enough. Might be most economical to tackle other issues as well, if @Tomasito665 wants to do that. And if so, update issue title here.
Or if not, file a new issue or issues. Happy to get improvements either way.

EDIT: looking at code, I suggest we get this merged in first, then whoever wants can do PR for floating-point number and/or boolean case.

@Tomasito665
Copy link
Contributor

@cowtowncoder sure! I'll see if I can include your suggestion on my PR to simplify the code tomorrow and file a separate PR for floats and booleans some time later during this weekend. Shall I link those to this issue or would you prefer to create a new one?

Tomasito665 added a commit to Tomasito665/jackson-databind that referenced this issue Oct 1, 2022
@cowtowncoder cowtowncoder changed the title Disable Integer to String coercion Allow disabling Integer to String coercion via CoercionConfig Oct 2, 2022
@cowtowncoder cowtowncoder removed the pr-welcome Issue for which progress most likely if someone submits a Pull Request label Oct 2, 2022
@cowtowncoder cowtowncoder added this to the 2.14.0 milestone Oct 2, 2022
@cowtowncoder
Copy link
Member

@Tomasito665 On issues, please create new one so PRs link nicely one-to-one. You may combine multiple new types (boolean and float) that's fine, but since I merged and closed this issue let's not link more changes to it.

@JooHyukKim
Copy link
Member

JooHyukKim commented Oct 28, 2023

@cowtowncoder Why is allowing scalar coercion on by default? It seems that this should be off by default. Obviously, this cannot be done for 2.x since that would break the world. But maybe for 3.x this should be off by default?

I am sure it's uninteded, but the questions sound like way too demanding for the questionee, asking for answers and explanations. 🤔

May I ask you to please file another issue @EarthCitizen and maybe link this issue back? There are "TEMPLATES" for requests, not like "I don't think this should be this way or that". All has been developed as intended.

@JooHyukKim
Copy link
Member

JooHyukKim commented Oct 28, 2023

Please know that I am not speaking on behalf of anybody, but just making an observation here. Thinking maybe the word "demanding" was not the best choice.

Question in its nature demands answers. What I saw problem is not how it is asked, but the question itself. You are right that the library is used by many people. Imagine that many people come and ask (I am sure politetely)

why feature A is this way? I think this should be that way

No context, no structure, not in the best form that can grow into a something meaningful.

Here we have a similiar question on a closed Github issue, again, no context no structure.

@cowtowncoder Why is allowing scalar coercion on by default? It seems that this should be off by default. Obviously, this cannot be done for 2.x since that would break the world. But maybe for 3.x this should be off by default?

It is great that you are contributing by sharing opinions, but would make a difference if it is more structured. With reasons, explanations, background and etc all in issue templates.

Following is what I wanted to say :

  • If you are just asking questions, there are Github disucssion section, or stackoverflow.
  • If you are making feature request (I think the case here), you can file a new issue.
  • If the question belongs here, enough relavent to reopen the issue, requires more explanation currently we don't have any.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
coercion-config Issue related to 2.12 added "CoercionConfig"
Projects
None yet
Development

No branches or pull requests

8 participants