Skip to content

Instantly share code, notes, and snippets.

@Yummy-Yums
Created June 5, 2026 09:54
Show Gist options
  • Select an option

  • Save Yummy-Yums/657383fa9226455ebfe6ff2954b903cb to your computer and use it in GitHub Desktop.

Select an option

Save Yummy-Yums/657383fa9226455ebfe6ff2954b903cb to your computer and use it in GitHub Desktop.
Code to generate Google Cloud Events based on the json schemas . run in a typelevel project
import os._
import io.circe._, io.circe.parser._
import scala.meta._
import org.scalafmt.Scalafmt._
import org.scalafmt.config.ScalafmtConfig
/*
- parse the events json. take note of inner objects
take the "defintions" key/value pair check if there's a "$ref" in definitions,
and parse it into json separately.
extract the values with the cursor into a list of key/value pairs, traverse though the types
and do a toType conversion is possible , check the returnTypes
- define imports
- define makeAbstrac tClass
- define makeApply
- define makeImpl
*/
object GoogleCloudEvents extends App {
val msg_published = Utils.parse_json_and_return_cursor("message_published.json")
val cursor = msg_published.hcursor
val definitions = cursor.downField("definitions")
val keysOfDefintions = definitions.keys.get.toList
val fileName = keysOfDefintions.head
val valuesOfDefintions = keysOfDefintions.map{ key =>
key -> definitions.downField(key).as[Json]
}
val wd = os.pwd / "src" / "main" / "scala"
var propsAndTypesOfValuesDefns = List.empty[List[(String, Type)]]
valuesOfDefintions.foreach {
case (name, valuePairs) => {
val properties = valuePairs.toOption.get
propsAndTypesOfValuesDefns =
propsAndTypesOfValuesDefns :+ Utils.getPropertiesAndTypes(properties)
}
}
if (!os.exists(wd / s"${fileName}.scala")){
println(s"creating file: ${fileName}.scala")
os.write(wd / s"${fileName}.scala", "")
}
var map = Map.empty[String, List[Decl.Def]]
for (
(key, defn) <- keysOfDefintions.zip(propsAndTypesOfValuesDefns)
) yield {
val res = defn.map(elem => {
val (propertyName, propertyType) = elem
Utils.makeMethod(propertyName, propertyType)
})
map = map + (key -> res)
}
val codeGen = map.map{
case(className, methods) => {
val abstractClass = Utils.makeAbstractClassWithDefMethods(className, methods)
val (applyMethod, decoder, implCaseClass, objectTermName) = Utils.makeApplyAndImpl(className, methods)
// (applyMethod, decoder, implCaseClass, objectNameTerm)
source"""
$abstractClass
object $objectTermName {
$applyMethod
$decoder
$implCaseClass
}
"""
}}
val source = source"""
..${Utils.imports}
..${codeGen.flatMap(_.stats).toList}
"""
val formatted = format(source.syntax, ScalafmtConfig.default).get
println(formatted)
os.write.over(wd / s"${fileName}.scala", formatted)
}
object Utils {
def parse_json_and_return_cursor(file_name: String) = {
val wd = os.pwd / "src" / "main" / "scala"
val read = os.read(wd / file_name)
val parsed_json = parse(read)
parsed_json match {
case Left(err) => println(s"Error parsing JSON: $err"); Json.Null
case Right(json) => json
}
}
// add newer imports here
val imports = List(
q"import io.circe._",
q"import io.circe.Decoder"
)
def makeMethod(
propertyName: String,
propertyType: Type
) = {
val methodName = Term.Name(propertyName)
// val typeParam = Type.Name(propertyType)
q"def ${methodName}: $propertyType"
}
def makeAbstractClass(
className: String,
methods: List[(String, String, String)]
) = {
val methods_ = methods.map {
case (methodName, returnType, "") => {
val methodName_ = Term.Name(methodName)
val typeParams = Type.Name(returnType)
q"def ${methodName_}: $typeParams"
}
case (methodName, returnType, typeArg) => {
val methodName_ = Term.Name(methodName)
val typeParams = Type.Apply(
Type.Name(returnType),
Type.ArgClause(List(Type.Name(typeArg)))
)
q"def ${methodName_}: $typeParams"
}
}
q"""
sealed abstract class ${Type.Name(className)} {
..${methods_}
}
"""
}
def makeAbstractClassWithDefMethods(
className: String,
methods: List[Decl.Def]
) = {
val name = Type.Name(className)
q"""
sealed abstract class $name {
..$methods
}
"""
}
def makeApplyAndImpl(
objectName: String,
methods: List[Decl.Def]
) = {
val returnType = Type.Name(objectName)
val impl = Type.Name("Impl")
val productPrefix = Lit.String(objectName)
val caseClassExtendsClass = Init(returnType, Name("Impl"), List.empty)
val params = methods.map{ method =>
val paramName = method.name
val paramType = method.decltpe
(paramName, paramType)
}
val params_apply = params.map {
case (paramName, paramType) => {
param"$paramName: $paramType"
}
}
val applyMethod = q"""
def apply(
..$params_apply
): $returnType = {
new $impl(..${params.map {
case (key, _) => key
}})
}
"""
val implCaseClass = q"""
private final case class $impl(
..$params_apply
) extends $caseClassExtendsClass {
override def productPrefix = $productPrefix
}
"""
val forProductN = Term.Name(s"forProduct${params.size}")
val objectNameTerm = Term.Name(objectName)
val decoder = q"""
implicit val decoder: Decoder[$returnType] =
Decoder.$forProductN(
..${params.map { case (paramName, _) => Lit.String(paramName.value) }}
)($objectNameTerm.apply)
"""
(applyMethod, decoder, implCaseClass, objectNameTerm)
}
def getPropertiesAndTypes(properties: Json): List[(String, Type)] = {
// get the cursor, get the keys, map over them , if the values of properties,
// contain $ref , split the values by '/' and take the last value
// other than just get the (propertyName -> propertyType)
var result = List.empty[(String, Type)]
val cursor = properties.hcursor
val keysOfProperties = cursor.keys.get.toList
keysOfProperties.map { currentKeyOfProperties =>
if (currentKeyOfProperties == "properties"){
val valueCursor = cursor.downField("properties").as[Json].toOption.get.hcursor
val keysInValues = valueCursor.keys.get.toList.map { key =>
val keyValueFields = valueCursor.downField(key).as[Json]
keyValueFields.map { pairs =>
val pairCursor = pairs.hcursor
pairCursor.keys.get.foreach {
case "$ref" => {
val ref = pairCursor.downField("$ref").as[String]
.toOption
.get
.split('/')
.last
result = result :+ (key -> Type.Name(ref))
}
case "type" => {
val type_ = pairCursor.downField("type").as[String].toOption.get
if (type_ == "object"){
val additionalProperties = pairCursor
.downField("additionalProperties").as[Json]
.toOption
.get
.hcursor
.downField("type").as[String].toOption.get
result = result :+ (key -> convertType(type_, additionalProperties))
} else {
result = result :+ (key -> convertType(type_))
}
}
case _ => ()
}
}
}
} else {
val value = cursor.downField(currentKeyOfProperties).as[String]
}
}
result
}
def convertType(property: String, additionalProperties: String = ""): Type ={
property match {
case "string" => Type.Name("String")
case "integer" => Type.Name("Int")
case "boolean" => Type.Name("Boolean")
case "number" => Type.Name("Double")
case "array" => Type.Apply(Type.Name("List"), Type.ArgClause(List(Type.Name("String"))))
case "object" =>
if (additionalProperties.nonEmpty)
Type.Apply(
Type.Name("Map"),
Type.ArgClause(List(Type.Name("String"), convertType(additionalProperties)))
)
else
Type.Apply(Type.Name("Map"), Type.ArgClause(List(Type.Name("String"), Type.Name("String"))))
case _ => Type.Name("String")
}
}
}
@Yummy-Yums

Copy link
Copy Markdown
Author

i'm using this piece of code to generate the google cloud events using the definitions key in each schema

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment