3

I'm having some trouble getting typing to work with the JsonSlurper in Groovy. I'm fairly new to Groovy, and even newer to adding strong types to it - bear with me.

Right now I've created a trait which defines the general shape of my JSON object, and I'm trying to cast the results of parseText to it.

import groovy.json.JsonSlurper

trait Person {
    String firstname
    String lastname
}

def person = (Person)(new JsonSlurper().parseText('{"firstname": "Lando", "lastname": "Calrissian"}'))

println person.lastname

This throws

Exception in thread "main" org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '{firstname=Lando, lastname=Calrissian}' with class 'org.apache.groovy.json.internal.LazyMap' to class 'Person' due to: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: Person(org.apache.groovy.json.internal.LazyMap)
    ...

I can see why my code doesn't make sense, I'm not trying to change the type of the data (casting), I'm just trying to let my IDE know that this is what's inside of my object.

Is it possible to at least add code completion to my JSON objects? I'd love to get runtime type checking, as well, but it's not necessary.

Sandy Gifford
  • 7,219
  • 3
  • 35
  • 65

2 Answers2

3

you could try to use delegate

this allows to wrap class around map

import groovy.json.JsonSlurper

class Person {
    @Delegate Map delegate
    String getFirstname(){ delegate.get('firstname') }
    String getLastname(){ delegate.get('lastname') }
}

def person = new Person(delegate:new JsonSlurper().parseText('{"firstname": "Lando", "lastname": "Calrissian"}'))

println person.lastname

or for example use Gson for parsing:

@Grab(group='com.google.code.gson', module='gson', version='2.8.5')

import com.google.gson.Gson

class Person {
    String firstname
    String lastname
}

def person = new Gson().fromJson('{"firstname": "Lando", "lastname": "Calrissian"}', Person.class)

assert person instanceof Person
println person.lastname
daggett
  • 26,404
  • 3
  • 40
  • 56
  • As a bonus using a `class` instead of a `trait` makes the cast work with `JsonSlurper` too since the cast can properly instantiate the object – Sandy Gifford Jul 23 '19 at 13:51
2

This actually is a cast and Groovy will try to turn your Map into said object.

From the docs:

The coercion operator (as) is a variant of casting. Coercion converts object from one type to another without them being compatible for assignment.

The way this works for a POJO is to construct a new object using the Map-c'tor. This will either unroll into calling setters or works directly with static compilation.

Be aware, that using maps with excess keys will lead to errors. So I'd only use this for toy projects. Use a proper JSON-mapper like e.g. Jackson instead.

So the solution here is to not use a trait (which is basically a interface) but a regular class.

cfrick
  • 35,203
  • 6
  • 56
  • 68