Dynamic types, groovy like json loading

Hi,

one thing right now is preventing me from using kotlin at this time. I have some Android apps written mostly in Java with some parts in Groovy. The groovy part is som plain old groovy objects for communication with our REST-Service.
The JSON documents come in from our server and are being mapped to groovy objects via

MyGroovyType result = new JsonSlurper().parseText(json),

which calls the map constructor of the groovy class and maps the properties found in the json to the groovy properties. Is there any way I could do this in Kotlin?

Additionaly I let my groovy types implement the trait “DynamicProperties”

    trait DynamicProperties {
        def propertyMissing(String name) {
            //println "unknown property $name"
        }

        def propertyMissing(String name, value) {
    //        println "unknown property $name"
        }
    }

just in case some property is being added to the rest api, so the mapper doesn’t crash and older app versions can work with newer rest service versions.
What is the way you would do this in Kotlin?

1 Like

Anything that can be done in Java can be done in Kotlin. That line of Java in Kotlin is just the following and should work the same:

val result = JsonSlurper().parseText(json)

i’m not sure about that supporting dynamic type at least on JVM 9 may result useful in some corner case like this, scripting or improve JVM/JS code compatibility.

Collect some opinions about this should be helpful.

val result = JsonSlurper().parseText(json)

This line would create a map with the json properties, but groovy implicitly calls the constructor of MyGroovyType with that map, which results in a typed object. I prefer typed objects whenever possible and groovy does the trick to map the (untyped) json at the earliest possible time.

Maybe one possibility would be to create an Annotation @MapConstructur on the class level to make the compiler generate a map constructur with named nullable arguments for this use case.

I’m not addressing exactly this issue, my rationale is more general.

Supporting “dynamic” type on JVM backend may be helpful for developers?
Moreover Java 9 should be a special support for “dynamic” (“dyn:” prefix in invocation).

I recently wrote a piece of code with assumption that a library may be loaded or not using JVM reflection: this was really ugly compared to the Groovy way.

2 Likes

I was assuming that line was in your Java code not in Groovy. The bottom line is that groovy should not be able to tell that it is called from Kotlin or Java. If somehow there is something that works in java but not Kotlin that is a serious bug and needs to be reported.

Have you tried adding Kotlin support to your project and then starting to convert Java files to Kotlin? The converter is not 100% reliable but once you work through any issues with the conversion there should be no difference in behavior.

The thing is, it’s possible to compile java/kotlin or java/groovy together in one compile run and to mix those two languages together. If a add a third language to my project, that means I need to separate the source folders and I can only call one language from another, not the other direction. Thats why I’m not gonna add a third language before the second is gone. So I’m still with java/groovy. Groovy still has some cool features that kotline hasn’t, and the dynamic loading of json without explicit conversion is the one i’d miss most.
That said, I’d really like to see a groovy <-> java converter in intellij idea, that would help a lot with the migration.

Just adding my voice here.

Please never implement “optional” typing!
The moment static typing becomes optional, that moment you can kiss goodbye on all benefits of static typing (reliable IDE refactorings, finding a lot of problems at compile time etc.).
Making dynamic typing an option will guarantee that people will use it, so in practice the static typing benefits will be gone.

I don’t know if Jackson runs on Android, but this is a classic case for it (or something similar). This should be solved through reflection, not through dynamic typing.

2 Likes

I am not sure if it can be used on Android devices but did you already take a look at this project?

That looks good, thanks for the hint. Will try it in the next days!

Gosu has this capability via the Dynamic type:

var personUrl = new URL( "http://gosu-lang.github.io/data/person.json" )
var person: Dynamic = personUrl.JsonContent

Here person can be used to dynamically access Json feature e.g.,

print( person.Name )

But Gosu can also provide static typing for Json via structural types:

var person = Person.fromJsonUrl( personUrl )
print( person.Name )
print( person.Address.City )
print( person.Hobby[0].Name )

^^ all statically typed and IDE code-completed. Groovy can’t touch this.

Read more here:
https://gosu-lang.github.io/2016/03/01/new-json-support-in-gosu.html

2 Likes

I disagree with some people here… if the JSON you work with changes often and you don’t control it, using Groovy and dynamic typing to handle seems perfectly reasonable.

If you try to use Jackson or Gson (which would work out-of-the-box if your type’s properties map correctly to the json) for that, any time your json changes, your code will crash.

That’s true for the Groovy implementation as well if any existing field changes type, but that should be very unlikely.

@cvmocanu just to add my experience with this feature in C#:

In C# this feature exists since 2010. The uses are very atypical but when you face problems like these (deal with json dynamically) it fits PERFECTLY and makes the solution very elegant. I’ve worked 6 years with C# and had seen just 2 uses of it, both awesome and no problems with it at all.

Implementing this feature in my opinion doesn’t make the language “untyped” as well as the implementation of static type check in Python doesn’t make it “typed”. It’s just a powerful (and dangerous) feature that developers can use when they decide to. Same as type casting and the not-null assertion operator ( !!): both are powerful and dangerous. It’s up to the developer. What is bad is having no elegant choices.

… an old thread, but this is what works for me:

background: I have a central json file declaring latest and milestone versions of dependencies, something like:

dependencies.json

{
    "latest": {
        "default": {
            "_comment": "default versions",
            "snakeYamlVersion": "1.25",     "_comment1": "http://www.snakeyaml.org",
            "springBootVersion": "2.2.2.RELEASE", "_comment1": "2.1.8.RELEASE",
            ...
        },
        "snapshots": {
            "_comment": "snapshot dependencies not available in mavenCentral/jcenter",
            "springSleuthVersion": "",
            ...
        },
        "plugin": {
            "_comment": "gradle plugin versions",
            "dockerPluginVersion": "6.1.1",           "_comment1": "https://github.com/bmuschko/gradle-docker-plugin/releases",
            ...
        },

I want that any project’s build.gradle.kts can consume this and reference versions of dependencies therein.

build.gradle.kts

import com.fasterxml.jackson.module.kotlin.*

val jsonmapper = jacksonObjectMapper()
val versions: Map<String, Map<String, Map<String, String>>> = jsonmapper.readValue(rootProject.file("dependencies.json"))
fun v (x: String): String? { return versions["latest"]?.get("default")?.get(x) }
fun vp(x: String): String? { return versions["latest"]?.get("plugin")?.get(x) }
fun vs(x: String): String? { return versions["latest"]?.get("snapshot")?.get(x) }

buildscript {
	repositories {
		mavenLocal()
		jcenter()
	}
	dependencies {
		classpath("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.0")
	}
}

...

dependencies {
	implementation(kotlin("stdlib"))
	implementation("org.yaml:snakeyaml:${v("snakeYamlVersion")}")
}

that’s it.

unfortunately this does not work if I want to use it inside buildscript { dependencies {...} }

any ideas?

Not sure but you may find that these definitions can be used in the magic buildscript block (and, indeed, settings.gradle) if you define them in a .kt file in the buildSrc/src/main/kotlin folder of your build. Anything in there basically gets built as a separate project before your main build, and becomes available in all build scripts. You can have simple definitions or even Gradle plugins. You can put them in any package, though if you put them in the default, unnamed package then you don’t have to import them into your build scripts.

See Organizing Gradle Projects for more information (and replace current with your Gradle version to get the right doc version).

I used this recently at work to parse ivy.xml files directly and it was mostly really easy. I only declared the elements and attributes I cared about and the rest were ignored. The one catch was due to a bug/misfeature which happens if you have an element which is a wrapper for a list (of other elements, all with the same name) but the list is empty, and there’s whitespace between the start and end tags. You get some odd exception which can be worked around with some code I got off Stack Overflow … but now can’t find. If I get the chance when I’m back at work tomorrow, I’ll post the link.

Anyway, except in quick hack programs, I definitely recommend parsing JSON, XML, or anything else to strongly-typed objects with validation, then using those. And if you have to file cope with different fields being missing or renamed, ideally do that at parsing time, son the rest of your code doesn’t have to be full of workarounds. Otherwise it’s easy to forget to put the workarounds in everywhere, and hard to notice when it becomes okay to take them out.

Hope that helps :slight_smile:

Okay, here’s the class which implements the workaround: Bitbucket and here’s where use of jackson is set up, including that class: Bitbucket

The ivy deserialisation model classes are in Bitbucket