Skip to content

Instantly share code, notes, and snippets.

@rafaeltoledo
Last active August 29, 2025 13:25
Show Gist options
  • Select an option

  • Save rafaeltoledo/478396e9cdefebc98c147c1de3a33c04 to your computer and use it in GitHub Desktop.

Select an option

Save rafaeltoledo/478396e9cdefebc98c147c1de3a33c04 to your computer and use it in GitHub Desktop.
Reddit Serialization
class RedditApi(
private val httpClient: HttpClient,
) {
suspend fun comments(subreddit: String, id: String): List<RedditResponse> {
val response = httpClient.get {
url {
host = HOST
pathSegments = listOf("r", subreddit, "comments", id)
}
}
return if (response.status.isSuccess()) {
response.body()
} else {
error("Request failed with code: ${response.status}")
}
}
suspend fun frontPage(limit: Int? = null, after: String? = null): RedditListing {
val response = httpClient.get {
url {
host = HOST
limit?.let { parameters.append("limit", it.toString()) }
after?.let { parameters.append("after", it) }
}
}
return if (response.status.isSuccess()) {
response.body<RedditObject>() as RedditListing
} else {
error("Request failed with code: ${response.status}")
}
}
suspend fun subreddit(subreddit: String, after: String?, limit: Int?): RedditResponse {
val response = httpClient.get {
url {
host = HOST
pathSegments = listOf("r", subreddit)
limit?.let { parameters.append("limit", it.toString()) }
after?.let { parameters.append("after", it) }
}
}
return if (response.status.isSuccess()) {
response.body()
} else {
error("Request failed with code: ${response.status}")
}
}
suspend fun top(limit: Int?, after: String?): RedditResponse {
val response = httpClient.get {
url {
host = HOST
pathSegments = listOf("top")
limit?.let { parameters.append("limit", it.toString()) }
after?.let { parameters.append("after", it) }
}
}
return if (response.status.isSuccess()) {
response.body()
} else {
error("Request failed with code: ${response.status}")
}
}
companion object {
private const val HOST = "api.reddit.com"
const val ENDPOINT = "https://$HOST"
}
}
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlin.reflect.KType
import kotlin.reflect.typeOf
@Serializable
data class RedditResponse(val data: RedditListing)
@Serializable(with = RedditSerializer::class)
sealed interface RedditObject
@Serializable
enum class RedditKind(val type: KType) {
@SerialName("t1")
T1(typeOf<RedditComment>()),
@SerialName("t3")
T3(typeOf<RedditLink>()),
@SerialName("Listing")
LISTING(typeOf<RedditListing>()),
@SerialName("more")
MORE(typeOf<RedditMore>()),
}
@Serializable
data class RedditComment(
val body: String,
@SerialName("body_html") val bodyHtml: String,
val controversiality: Int,
val depth: Int,
@SerialName("link_id") val linkId: String,
@SerialName("parent_id") val parentId: String,
/**
* Ugh-asaurus
*
* @return list of comments. Or false. Because yeah.
*/
val replies: RedditObject?,
@SerialName("subreddit_id") val subredditId: String,
// Inherited from RedditSubmission. A little grody
override val author: String,
@SerialName("author_flair_text") override val authorFlairText: String?,
@SerialName("banned_by") override val bannedBy: String?,
override val created: Long,
@SerialName("created_utc") override val createdUtc: Long,
override val gilded: Int,
override val id: String,
override val name: String,
override val saved: Boolean,
override val score: Int,
override val subreddit: String,
override val ups: Int,
) : RedditObject, RedditSubmission
@Serializable
data class RedditLink(
val clicked: Boolean,
val domain: String?,
val hidden: Boolean,
@SerialName("is_self") val isSelf: Boolean,
@SerialName("link_flair_text") val linkFlairText: String?,
@SerialName("num_comments") val commentsCount: Int,
val permalink: String,
val selftext: String?,
@SerialName("selftext_html") val selftextHtml: String?,
val stickied: Boolean,
val thumbnail: String,
val title: String,
val url: String,
val visited: Boolean,
@SerialName("post_hint") val postHint: String?,
// Inherited from RedditSubmission. A little grody
override val author: String,
@SerialName("author_flair_text") override val authorFlairText: String?,
@SerialName("banned_by") override val bannedBy: String?,
override val created: Long,
@SerialName("created_utc") override val createdUtc: Long,
override val gilded: Int,
override val id: String,
override val name: String,
override val saved: Boolean,
override val score: Int,
override val subreddit: String,
override val ups: Int,
) : RedditObject, RedditSubmission
@Serializable
data class RedditListing(
val after: String? = null,
val before: String? = null,
val children: List<RedditObject>,
val modhash: String,
) : RedditObject
@Serializable
data class RedditMore(
val count: Int,
val name: String,
val id: String,
@SerialName("parent_id") val parentId: String,
val depth: Int,
val children: List<String>,
) : RedditObject
interface RedditSubmission {
val author: String
@SerialName("author_flair_text")
val authorFlairText: String?
@SerialName("banned_by")
val bannedBy: String?
val created: Long
@SerialName("created_utc")
val createdUtc: Long
val gilded: Int
val id: String
val name: String
val saved: Boolean
val score: Int
val subreddit: String
val ups: Int
}
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.descriptors.element
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.decodeStructure
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.serializer
object RedditSerializer : KSerializer<RedditObject> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("RedditObject") {
element<RedditKind>("kind")
element<JsonElement>("data")
}
override fun deserialize(decoder: Decoder): RedditObject {
if (decoder !is JsonDecoder) {
error("Unsupported decoder format: ${decoder::class.simpleName}")
}
return decoder.decodeStructure(descriptor) {
var kind: RedditKind? = null
var data: JsonElement? = null
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> kind = decodeSerializableElement(descriptor, 0, RedditKind.serializer())
1 -> data = decodeSerializableElement(descriptor, 1, JsonElement.serializer())
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
require(kind != null && data != null)
decoder.json.decodeFromJsonElement(serializer(kind.type), data) as RedditObject
}
}
override fun serialize(encoder: Encoder, value: RedditObject) {
if (encoder !is JsonDecoder) {
error("Unsupported encoder format: ${encoder::class.simpleName}")
}
TODO("Not implemented yet!")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment