Alright, here my question pertains to this code: `...
# announcements
n
Alright, here my question pertains to this code:
Copy code
class JsonValue {
    constructor()

    sealed class JsonData {
        class IntWrapper(val x: Int) : JsonData()
        class StringWrapper(val x: String) : JsonData()
        class ListWrapper(val x: MutableList<JsonValue>) : JsonData()
        class ObjectWrapper(val x: MutableMap<String, JsonValue>) : JsonData()
        class NullWrapper() : JsonData()
    }

    var jsonData: JsonData = JsonData.NullWrapper()
    
    companion object {
        fun make(x: Int): JsonValue {
            val j = JsonValue()
            j.jsonData = JsonData.IntWrapper(x)
            return j
        }
        fun make(x: String): JsonValue {
            val j = JsonValue()
            j.jsonData = JsonData.StringWrapper(x)
            return j
        }
        inline fun <reified T> make(x: List<T>): JsonValue {
            val j = JsonValue()
            j.jsonData = JsonData.ListWrapper(x.map { JsonValue.make(it) })
            return j
        }
    }
}
So, json is basically a recursive sum type. Sum types in kotlin are typically done via sealed classes, so I'm doing my best with those. I'm trying to have a generic factory function here that works recursively. The problem is, my generic
make
that takes a List complains, because the T is not constrained. However, I can only constrain types in Kotlin by interfaces/classes, and I can't add add any new bases/interfaces to types like Int and String, that I want this to work with conveniently. So, is there any nice way to work around this? Or should I basically declare defeat, change the signature to
fun make(x: List<JsonValue>)
and put the onus on the user to convert e.g. a
List<int>
into a
List<JsonValue>
?
Alright, I make a further attempt, and I think this is pretty definitely impossible, because as you start to nest containers
type erasure will kick in
and I think you're basically screwed
I tried to change
make
like this
Copy code
inline fun <reified T> make(x: List<T>): JsonValue {
            val j = JsonValue()
            if (T::class == Int::class) {
                j.jsonData = JsonData.ListWrapper(x.map { JsonValue.make(it as Int) }.toMutableList())
            }
            if (T::class == String::class) {
                j.jsonData = JsonData.ListWrapper(x.map { JsonValue.make(it as String) }.toMutableList())
            }
            if (T::class == List::class) {
                j.jsonData = JsonData.ListWrapper(x.map { JsonValue.make(it as List<>) }.toMutableList())
            }
            return j
        }
this works for the "flat" cases but if you have a List inside a List, I'm not sure what can be done
hmm and when I make the cast for List, List<*> I also get an error from the IDE that make cannot be recursive
ah it's because its inline
a
seems like you need a
make(Any)
function that would use the run-time type to return a JsonValue
d
I strongly recommend not going down this path in the first place. Having a type signature like this says "I can serialize anything, watch this magic" to me, which is a lie. The caller is in a better position to do it than your generic function.
☝️ 1
n
@Dico err it's not at all "I can serialize anything". Serialization is not even involved here. This is just saying that it can handle converting native data structures into this json structure, in the small handful of cases it knows, recursively
That's all. This works fine in other languages. So please let's not do that thing where we blame the code for (potential) language shortcomings. I've noticed that's bizarrely common around here.
@araqnid That sounds like it would help, the problem though (which really put a nail in this coffin) is the erased generics.
If you get passed a List<Int>, as an Any, i don't think there's any way to cast down the results safely afterwards, is there?
Or maybe that does work. I'll give it a shot, thanks 🙂
@araqnid You're right, good call. I was able to get it to work, but obviously there's the substantial downside that you no longer get compilation errors, rather runtime errors
The question is, is there any way around this, or is this just a limitation of the kotlin type system?
The problem basically seems to be the fact that in order to constrain the types more reasonably, I'd want to provide some kind of interface/base. But there's no way to make already-existing types, like Int, List, etc, respect an interface that I write.
So you can either have a more strongly typed approach to Json, or you can have one that plays more easily with standard library types, but you can't have both
d
I'm getting at serialization because you obviously have an in menory json structure here. I'm more so worried about the lack of type safety around this stuff though, it's very lenient. I think you know what you're doing though 🙂 Splitting it up into functions that convert types you support, with an internal function for
Any
that calls the corresponding one, might be decent.
n
@Dico well that's the whole point I guess, kotlin is not capable of supporting this in a type safe and convenient way. You have to give up on one or the other.
If you imagine a user with a
x: List<Map<String, List<Int>>>
, without a recursive function that works correctly, the user is going to have to do the conversions themselves, at every level
It's going to be something like
j = x.map { JsonValue( it.mapValues { JsonValue ( it.map { JsonValue(it) } ) } ) }
that's awful