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

Unable to deserailize Seq with AS_EMPTY null handling #462

Closed
nick-benoit14 opened this issue Aug 14, 2020 · 11 comments
Closed

Unable to deserailize Seq with AS_EMPTY null handling #462

nick-benoit14 opened this issue Aug 14, 2020 · 11 comments
Assignees
Labels

Comments

@nick-benoit14
Copy link
Contributor

Configuring jackson to treat null collections as empty appears to work for java.util collections, but fails for scala collections like Seq

package com.centricient.service.bot

import java.util

import com.centricient.service.bot.JacksonTest.{D, E}
import com.fasterxml.jackson.annotation.{JsonSetter, Nulls}
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import org.junit.runner.RunWith
import org.scalatest.{BeforeAndAfter, FlatSpec, Matchers}
import org.scalatest.junit.JUnitRunner

import scala.collection.mutable


object JacksonTest {
  case class D(s: util.ArrayList[String])
  case class E(s: Seq[String])
}


@RunWith(classOf[JUnitRunner])
class JacksonTest extends FlatSpec with BeforeAndAfter with  Matchers {

  val mapper = new ObjectMapper()
  mapper.registerModule(new DefaultScalaModule());
  mapper.setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));

  it should "do stuff" in {

    val result1 = mapper.readValue("{\"s\": null}", classOf[D]) // Yields => D([])

    // Fails
    // com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot create empty instance of [collection-like type; class scala.collection.Seq, contains [simple type, class java.lang.String]], no default Creator
    // at [Source: (String)"{"s": null}"; line: 1, column: 7] (through reference chain: com.centricient.service.bot.JacksonTest$E["s"])
    val result2 = mapper.readValue("{\"s\": null}", classOf[E])

  }
}
@nick-benoit14
Copy link
Contributor Author

Jackson version 2.10.4
Jackson Module Scala jackson-module-scala_2.12:2.10.4

@pjfanning
Copy link
Member

pjfanning commented Aug 14, 2020

jackson-module-scala 2.11 supports case class param default values - would it be possible to try case class E(s: Seq[String] = Seq.empty)?

@nick-benoit14
Copy link
Contributor Author

I get the same error with the default parameter

@pjfanning
Copy link
Member

@cowtowncoder would you be able to provide some pointers on how to support a NullValueProvider. I tried extending the scala module Deserializer to implement the NullValueProvider but that didn't help. That code is in #465

@cowtowncoder
Copy link
Member

NullValueProvider interaction is indeed quite... elaborate, and probably the easiest way is to find counterparts in jackson-databind for similar handlers. I'll see if I can find suggestions for case you linked.

@cowtowncoder
Copy link
Member

@pjfanning Not sure what specifically is failing, but to get "as-empty" handling, deserializer for type needs to first implement getEmptyValue(DeserializationContext). If so, I think src/main/java/com/fasterxml/jackson/databind/deser/impl/NullsAsEmptyProvider.java should be usable as-is for scala types too.

Another place of interest might be src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java (method _findNullProvider()) -- depending on whether Scala deserializers extend it (in general it is recommendable to use it as the base for all deserializers), might be able to call that method.

Apologies for vague explanation: implementation is bit messy. But I think making getEmptyValue(...) work (if it does not already) would be a good first step.

@pjfanning pjfanning added the 2.12 label Aug 17, 2020
@pjfanning pjfanning self-assigned this Aug 17, 2020
@pjfanning
Copy link
Member

@cowtowncoder thanks, I think I got it working

@nick-benoit14 could you try the latest 2.12.0-SNAPSHOT?

@pjfanning
Copy link
Member

@cowtowncoder what is the v3.0.0 equivalent of
mapper.setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY)) ?

@cowtowncoder
Copy link
Member

cowtowncoder commented Aug 18, 2020

@pjfanning It is via MapperBuilder, from unit tests:

    public void testInjectWithCreator() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .changeDefaultNullHandling(h -> JsonSetter.Value.construct(Nulls.AS_EMPTY, Nulls.AS_EMPTY))
                // ...
                .build();
    }

and there is also option to "rebuild" from existing mapper, using

    ObjectMapper newMapper = oldMapper.rebuild()
          // use the builder same as if starting with `JsonMapper.builder()`
          .build();

@pjfanning
Copy link
Member

pjfanning commented Aug 18, 2020

@cowtowncoder I copied over the new tests that work in 2.12 branch to the master branch but changed to use the changeDefaultNullHandling method you suggested. The tests fail. The getEmptyValue methods that I added don't appear to be called - but it could be another issue, there is still a lot of stuff broken in master version of jackson-module-scala.

@cowtowncoder
Copy link
Member

@pjfanning Sorry to hear that. Not sure what could be causing it unfortunately.

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

3 participants