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

Empty Lists can only be String-typed in CSV #199

Closed
sonOfRa opened this issue May 15, 2020 · 6 comments
Closed

Empty Lists can only be String-typed in CSV #199

sonOfRa opened this issue May 15, 2020 · 6 comments
Labels
Milestone

Comments

@sonOfRa
Copy link

sonOfRa commented May 15, 2020

When mapping POJOs that contain Lists to CSV, Jackson fails with an exception if that list is empty, and the type of the list is not String:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `java.util.ArrayList` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('') at [Source: (com.fasterxml.jackson.dataformat.csv.impl.UTF8Reader); line: 2, column: 1] (through reference chain: de.slevermann.jackson.csv.Model["LONGS"])

I've provided example code with a failing and a succeeding test case over at https://github.com/sonOfRa/jackson-csv-bug.

I'll provide a shortened version here so it's also preserved in an issue. The following CSV:

STRINGS;OTHER_FIELD
;Hello

successfully gets read by Jackson into the following POJO (getters and setters omitted):

public class Model {

    @JsonProperty("STRINGS")
    private List<String> strings;

    @JsonProperty("OTHER_FIELD")
    private String otherField;
}

The list is empty, and the other field contains "Hello".

However, with the following CSV:

LONGS;OTHER_FIELD
;Hello

Jackson produces the above exception, when attempting to read into the following POJO:

public class Model {

    @JsonProperty("LONGS")
    private List<Long> longs;

    @JsonProperty("OTHER_FIELD")
    private String otherField;
}
@cowtowncoder
Copy link
Member

Thank you for reporting this -- sounds like a bug -- and big thank you for providing full reproduction too!

@cowtowncoder cowtowncoder added 2.10 and removed 2.10 labels May 16, 2020
@cowtowncoder
Copy link
Member

One quick note: I think changing type to long[] works around the issue. Will try to see how to make List work too.

@sonOfRa
Copy link
Author

sonOfRa commented May 16, 2020

I'll try that out on monday. Our current workaround is two steps:

  1. mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);,
  2. this.ids = Objects.requireNonNullElseGet(ids, ArrayList::new);
    That way we receive a null object from deserialization, and the setter for the list in our object catches it and creates an empty list instead

@cowtowncoder
Copy link
Member

Ok I can see why this happens. Good thing is that I think I know how to fix is, but I am not yet sure if I can do it in safe enough way to include in 2.11(.1), or if it needs to go in 2.12 (bigger changes, higher regression risk).

Longer story is that formats like CSV and XML with no native notion of strong typing (at least wrt List/array constructs) will need to rely on JsonParser method isExpectedStartArrayToken(), which basically hints "Ok I am expecting an array, so given that, do you think you might have array". This is then used by textual parsers to possibly infer array whereas normally you would just get String value.
So far so good: but there is also logic for String values in CollectionDeserializer that handles "empty String" case. In this case, empty String case trumps array logic.
But for special case of String values, different, optimized deserializer (StringCollectionDeserializer) is used, and it does NOT have default-from-empty-String check.

Anyway: I'll see what I can do here. Thank you again for reporting this case!

@cowtowncoder
Copy link
Member

Sigh. For a moment I thought fix was simply and could go in 2.11 of jackson-databind: but turns out there are 2 small but clear regressions that more targeted fix would cause, for specific (if convoluted) case of handling DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY in combination with empty String to result in null container (current behavior).

So I think I will need to devise a bit bigger fix to be done in 2.12.

@cowtowncoder
Copy link
Member

Fixed for 2.12.0. In the meantime, work-arounds include:

  • Use of long[] instead of List<Long>
  • (possibly) forcing use of blank String (all white space) instead of empty (likely avoids wrong handling too).
  • Post-processing null into empty List after readValue

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

No branches or pull requests

2 participants