wooki
wooki

Reputation: 448

Groovy + JsonSlurper strange behaviour

I have following code to parse a JSON file:

@Override
Map<String, Configuration> parseJson() {
    Object configurationFile = readConfigurationFile()
    configurationFile.schemas.each { schemaProtectionInformation ->
        processing(schemaProtectionInformation)
    }
}

private Object readConfigurationFile() {
    InputStream is = getClass().getClassLoader().getResourceAsStream("test.json")
    BufferedReader reader = new BufferedReader(new InputStreamReader(is))
    return new JsonSlurper().parse(reader)
}

To process following JSON file:

{
  "schemas": [
    {
      "name": "plan_pm_test",
      "protectedDimensions": [
        {
          "name": "dActivityWbs",
          "usedToSecureFactTable": true,
          "aliasInFactTable": "PLAN_WBS",
          "levels" : ["LEVEL_1_ID","LEVEL_2_ID","LEVEL_3_ID","LEVEL_4_ID","LEVEL_5_ID","LEVEL_6_ID","LEVEL_7_ID","LEVEL_8_ID","LEVEL_9_ID"]
        },
        {
          "name": "dResponsibleOrganicUnit",
          "usedToSecureFactTable": true,
          "aliasInFactTable": "RES_ORG_UNIT",
          "levels" : ["ID","LEVEL_1_ID","LEVEL_2_ID"]
        },
        {
          "name": "dContributionOrganicUnit",
          "usedToSecureFactTable": true,
          "aliasInFactTable": "CON_ORG_UNIT",
          "levels" : ["ID","LEVEL_1_ID","LEVEL_2_ID"]
        }
      ]
    }
  ]
}

If I execute this code I will receive following error:

Cannot cast object '[{name=plan_pm_test, protectedDimensions=[{aliasInFactTable=PLAN_WBS, levels=[LEVEL_1_ID, LEVEL_2_ID, LEVEL_3_ID, LEVEL_4_ID, LEVEL_5_ID, LEVEL_6_ID, LEVEL_7_ID, LEVEL_8_ID, LEVEL_9_ID], name=dActivityWbs, usedToSecureFactTable=true}, {aliasInFactTable=RES_ORG_UNIT, levels=[ID, LEVEL_1_ID, LEVEL_2_ID], name=dResponsibleOrganicUnit, usedToSecureFactTable=true}, {aliasInFactTable=CON_ORG_UNIT, levels=[ID, LEVEL_1_ID, LEVEL_2_ID], name=dContributionOrganicUnit, usedToSecureFactTable=true}]}]' with class 'java.util.ArrayList' to class 'java.util.Map' due to: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: java.util.Map(groovy.json.internal.LazyMap)
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '[{name=plan_pm_test, protectedDimensions=[{aliasInFactTable=PLAN_WBS, levels=[LEVEL_1_ID, LEVEL_2_ID, LEVEL_3_ID, LEVEL_4_ID, LEVEL_5_ID, LEVEL_6_ID, LEVEL_7_ID, LEVEL_8_ID, LEVEL_9_ID], name=dActivityWbs, usedToSecureFactTable=true}, {aliasInFactTable=RES_ORG_UNIT, levels=[ID, LEVEL_1_ID, LEVEL_2_ID], name=dResponsibleOrganicUnit, usedToSecureFactTable=true}, {aliasInFactTable=CON_ORG_UNIT, levels=[ID, LEVEL_1_ID, LEVEL_2_ID], name=dContributionOrganicUnit, usedToSecureFactTable=true}]}]' with class 'java.util.ArrayList' to class 'java.util.Map' due to: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: java.util.Map(groovy.json.internal.LazyMap)
    at cern.ais.datawarehouse.baserver.mondriansecurity.common.schemaprotectionconfiguration.JsonResourceFileConfigurationRepositoryPopulator.readConfiguration(JsonResourceFileConfigurationRepositoryPopulator.groovy:23)
    at cern.ais.datawarehouse.baserver.mondriansecurity.common.schemaprotectionconfiguration.JsonResourceFileConfigurationRepositoryPopulatorTest.tes(JsonResourceFileConfigurationRepositoryPopulatorTest.groovy:12)

So of course I started debugging application step by step to see which part of code in part processing() throws this exception. Surprisingly all the code there executes normally: without throwing exception and returning results I except.

What suprised me even more is that when I changed slightly the code of first method, it works without producing the exception.

@Override
Map<String, Configuration> readConfiguration() {
    Object configurationFile = readConfigurationFile()
    configurationFile.schemas.each { schemaProtectionInformation ->
        processing(schemaProtectionInformation)
    }
    println "test 2"
}

I have no idea how the println method can change anything there. Of course it does not have to be necessarily println method that does the trick. So if I do something like this:

@Override
Map<String, Configuration> readConfiguration() {
    Object configurationFile = readConfigurationFile()
    configurationFile.schemas.each { schemaProtectionInformation ->
        processing(schemaProtectionInformation)
    }
    test()
}

void test() {

}

It will work as well (no expcetion thrown). I have no idea why having some additional code after processing the json file should make any change here.

Just now I have actually commented out the processing method, so that the method body looks like below.

@Override
Map<String, Configuration> readConfiguration() {
    Object configurationFile = readConfigurationFile()
    configurationFile.schemas.each { schemaProtectionInformation ->
        //processing(schemaProtectionInformation)
    }
}

And even though I receive the same exception. Hence, the error is not related to the implementation of processing method.

I would greatly appreciate your input.

Upvotes: 2

Views: 3050

Answers (2)

J&#233;r&#233;mie B
J&#233;r&#233;mie B

Reputation: 11022

In Groovy, the return is implicit, it's the last statement of a function. So your code is equivalent to :

@Override
Map<String, Configuration> parseJson() {
    Object configurationFile = readConfigurationFile()
    return configurationFile.schemas.each { schemaProtectionInformation ->
        processing(schemaProtectionInformation)
    }
}

The each function return the element on which is called. In your case, schemas. However, schema is a collection, not a map: You see the ClassCastException. Your code is equivalent to :

@Override
Map<String, Configuration> parseJson() {
    Object configurationFile = readConfigurationFile()
    configurationFile.schemas.each { schemaProtectionInformation ->
        processing(schemaProtectionInformation)
    }
    return configurationFile.schemas
}

When you add something after this statement, you are just creating another implicit return. You should use an explicit return configurationFile.

Upvotes: 1

wooki
wooki

Reputation: 448

Wow, sorry. Such a rookie mistake. Too bad I did not have unit tests for this class, cause I would spot the missing bit faster then.

Obviously the missing part is a return keyword. The code right now looks like this:

@Override
Map<String, Configuration> readConfiguration() {
    Object configurationFile = readConfigurationFile()
    configurationFile.schemas.each() { schemaProtectionInformation ->
        processSchemaDetailsFromFile(schemaProtectionInformation)
    }
    return schemasConfigurations
}

And works without any issues.

If my memory serves me right, this code evolved from one where reloadConfiguration() did not return a value. Then probably I changed the return type but forgot to add an explicit return statement. Since groovy allows not having a return keyword it did not complain and tried to return some list and then failed cause the specified type of value to be returned by this method was map.

Well... I blame lack of sleep.

Upvotes: 0

Related Questions