jackson-dataformats-text icon indicating copy to clipboard operation
jackson-dataformats-text copied to clipboard

YAML file with no content throws `MismatchedInputException` when binding to Object type (POJO etc)

Open serandel opened this issue 6 years ago • 8 comments

A curious behaviour I've encountered adding Jackson to a Kotlin project...

I'm trying to deserialize a YAML file to a Kotlin data class

data class Settings(val wireframe: Boolean = false)

with the following code

mapper = ObjectMapper(YAMLFactory())
mapper.registerModule(KotlinModule()) 

val settings = settingsFileInputStream.use {
   mapper.readValue(it, Settings::class.java)
}

If the file content is

wireframe: true

it works.

But if I comment out the settings like this:

#wireframe: true

then I get the following exception:

com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input

I believed it was because default parameters in the data class weren't accepted, but no. To prove that, I modified the settings like this:

data class Settings(val wireframe: Boolean = false, val foo: String = "bar")

and I can deserialize from this file:

#wireframe: true
foo: foobar

but I get the exception again if I use

#wireframe: true
#foo: foobar

Jackson version: 2.9.10 (latest stable, AFAIK)

serandel avatar Oct 08 '19 13:10 serandel

Since this is YAML-specific (but not necessarily Kotlin-specific), I'll transfer to jackson-dataformats-yaml.

Problem itself is probably since what you have is essentially an empty document, and I don't know what it looks like at tokenization level -- since no properties exist, there is nothing to infer an "Object" value, and this is difficult to model.

cowtowncoder avatar Oct 08 '19 17:10 cowtowncoder

Here's how I worked around this issue in a Java project, in case it helps anyone.

Workaround 1

If my YAML file would be empty except for comments, add a {} at the top level to prevent the mapper from blowing up.

# This is a comment.
# foo=bar

# This {} is necessary because everything else is commented out.
{}

This "worked" but felt bad. I felt compelled to explain the {} in my example YAML config file with a comment, but I would have preferred to not distract the reader with that stuff at all. I'm no longer using this approach.

Workaround 2

Do a two-step deserialization (1) from YAML to JsonNode then (2) from JsonNode to an instance of my class. If the JsonNode is empty, which it will be when the YAML file contains only comments and white space, return a default value. Otherwise, ask the ObjectMapper to read the JsonNode.

Path yamlFile = ...
YAMLMapper yamlYapper = new YAMLMapper();
JsonNode tree;
try (InputStream inputStream = Files.newInputStream(yamlFile)) {
  tree = yamlMapper.readTree(inputStream);
}

if (tree.isEmpty()) {
  return MyConfig.defaultConfig();
}

ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.treeToValue(tree, MyConfig.class);

This is the approach that I'm using currently.

michaelhixson avatar Dec 23 '19 19:12 michaelhixson

@michaelhixson Thank you for sharing this! This makes sense as Tree Model can read "no content" as MissingNode. There are 2 other similar approaches now that you mention this, either:

  1. create parser from YAMLFactory (via mapper.getFactory()), check if (parser.nextToken() == null) { // no content
  2. create MappingIterator<MyConfig> via mapper.readerFor(MyConfig.class).readValues()), and see if if (hasNext()) { ... has value ... } else { ... no value create default ... }

in fact (2) might be the cleanest version:

try (MappingIterator<MyConfig> iterator = mapper.readerFor(MyConfig.class)
    .readValues()) {
  if (!iterator.hasValue()) {
    return MyConfig.defaultConfig();
  }
  return iterator.nextValue();
}

cowtowncoder avatar Dec 24 '19 00:12 cowtowncoder