Bug Upgrading from 1.18 => 1.19

Issue #392 resolved
Michael Bryzek
created an issue

We are in the process of upgrading a scala/play app today from 1.18 to 1.19. Our project is failing with the error:

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to scala.runtime.Nothing$

The code in question:

          val y = Option(yaml.load(contents))

          val obj = y match {
            case None => Map[String, Object]()
            case Some(data) => data.asInstanceOf[java.util.Map[String, Object]].asScala
          }

where contents looks like:

builds:
  - root

This is for an open source project - source at https://github.com/flowcommerce/delta/pull/185

full build error at https://travis-ci.org/flowcommerce/delta/builds/288091683 (and also attached)

If you all have a moment - would appreciate any guidance - and thank you again for all the work you've done on snakeyaml

Comments (8)

  1. Andrey Somov repo owner

    It works:

    [info] Run completed in 8 seconds, 419 milliseconds.
    [info] Total number of tests run: 2
    [info] Suites: completed 1, aborted 0
    [info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
    [info] All tests passed.
    [info] Passed: Total 20, Failed 0, Errors 0, Passed 20
    
  2. Michael Bryzek reporter

    Thanks Andrey - assuming that is the build result for snakeyaml?

    it seems something has changed in the underlying implementation and our code which worked w/out problem against 1.18 now breaks in 1.19. Is there any information you have on what changed in terms of the underlying types used in the parser?

  3. Andrey Somov repo owner

    The API should be exactly the same. No changes. If the underlying types have been changed I would expect tests to give us a sign.

    I cannot be 100% sure, of course.

  4. maslovalex

    @Michael Bryzek , the signature of the load method has changed. It is parameterized now. It has no implications for Java, but Scala for non-parameterized invocation uses type Nothing. As a result type of y (on line 27) is Option[Nothing] instead of Option[Object] or Option[AnyRef] and LinkedMap[,] could not be casted to Nothing

    From the brief look I see 2 options (changes in Parser.scala) :

    1

    // line 27
    val y = Option(yaml.load[Object](contents))
    

    2

    diff --git a/lib/src/main/scala/config/Parser.scala b/lib/src/main/scala/config/Parser.scala
    index 630def1..ec97bf9 100644
    --- a/lib/src/main/scala/config/Parser.scala
    +++ b/lib/src/main/scala/config/Parser.scala
    @@ -24,16 +24,14 @@ case class Parser() {
             val yaml = new Yaml()
    
             Try {
    -          val y = Option(yaml.load(contents))
    +          type JMap[K, V] = java.util.Map[K, V]
    +          val obj = Option(yaml.loadAs(contents,classOf[JMap[String, AnyRef]]))
    +            .map(_.asScala)
    +            .getOrElse(Map[String, AnyRef]())
    
    -          val obj = y match {
    -            case None => Map[String, Object]()
    -            case Some(data) => data.asInstanceOf[java.util.Map[String, Object]].asScala
    -          }
    -          val stagesMap: Map[String, Object] = obj.get("stages") match {
    -            case None => Map[String, Object]()
    -            case Some(data) => data.asInstanceOf[java.util.Map[String, Object]].asScala.toMap
    -          }
    +          val stagesMap = obj.get("stages")
    +            .map(_.asInstanceOf[JMap[String, AnyRef]].asScala)
    +            .getOrElse(Map[String, AnyRef]())
    
               val config = ConfigProject(
                 stages = toProjectStages(
    

    I like #2 better, but it is of course up to you how deal with it.

  5. Michael Bryzek reporter

    really appreciate your notes! I had missed the loadAs method. Final diff in our case is the really simple

    -          val y = Option(yaml.load(contents))
    +          val y = Option(yaml.loadAs(value, classOf[java.util.Map[String, Object]]))
    

    thank you!

  6. Log in to comment