In the context of a Grails application, we parse JSON into command objects. The automatic conversion from a JSON map to the POGO fails with an error like this:
org.codehaus.groovy.runtime.typehandling.GroovyCastException
:
Cannot cast object '{<snip>}
' with class 'groovy.json.internal.LazyMap
' to class 'SomeCmd
' due to:
java.lang.IllegalArgumentException
: No enum constantFoo.my-bar
I narrowed it down to this plain Groovy MWE:
import groovy.json.JsonSlurper
enum Foo {
Bar("my-bar"),
Ista("my-ista")
final String s
private Foo(String s) {
this.s = s
}
}
class SomeCmd {
Foo foo
}
def some = new SomeCmd(new JsonSlurper().parseText('{ "foo" : "my-bar" }'))
println(some.foo)
This errors with
java.lang.IllegalArgumentException
: No enum constantFoo.my-bar
This is expected -- so far, so good.
Now, following the documentation, I thought adding custom coercion from String
to Foo
might resolve the issue (also from here):
enum Foo {
<snip>
static Foo fromJsonString(String s) {
return values().find { it.s == s }
}
}
def oldAsType = String.metaClass.getMetaMethod("asType", [Class] as Class[])
String.metaClass.asType = { Class type ->
type == Foo ?
Foo.byJsonString(delegate as String) :
oldAsType.invoke(delegate, [type] as Class[])
}
However, the error persists. Apparently, JsonSlurper
does not use coercion at all, given that
println("my-bar" as Foo)
prints Bar
as desired.
What is going on here? How can I get JsonSlurper
to pick the correct enum cases by something besides the case name?
PS: Fun fact, if we change the second-to-last line to
new JsonSlurper().parseText('{ "foo" : "my-bar" }') as SomeCmd
the script prints null
.
Groovy will happily use custom setters to construct the object. With Foo.fromJsonString
as given in the question, define SomeCmd
like so:
class SomeCmd {
Foo foo
void setFoo(Object jsonObject) {
if (jsonObject == null || jsonObject instanceof Foo) {
this.foo = jsonObject
return
} else if (jsonObject instanceof String) {
Foo f = Foo.fromJsonString(jsonObject)
if ( null != f ) {
this.foo = f
return
}
}
throw new IllegalArgumentException("No Foo for $jsonObject")
}
}
Then, the code as given prints Bar
as desired.
However, this doesn't help in Grails with parsing JSON into command objects -- Grails doesn't use coercion nor Groovy's map "magic". See this follow-up question.