Skip to content

Instantly share code, notes, and snippets.

@peter
Last active December 8, 2017 08:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peter/f1b852ce8ca9a1a1e6d8259489bbc2fa to your computer and use it in GitHub Desktop.
Save peter/f1b852ce8ca9a1a1e6d8259489bbc2fa to your computer and use it in GitHub Desktop.
Clojure vs Scala, Dynamic vs Static Typing

Untyped Scala vs Properly Typed Scala vs Dynamic Typing

The point of comparison here is a simple function that looks up a string key in a nested map/dictionary (JSON data from the payload/claims of a JWT token) and returns the given key if it's there and None/nil otherwise.

Scala (Without Proper Types)

Original function:

def verifyPartner(partner: String, payload: Map[String, java.lang.Object]): Either[String, String] = {
  val appMetadata = payload.getOrElse("app_metadata", "")
  val partnerOpt = withMap(appMetadata) { metadata =>
    metadata.getOrElse("partners", "") match {
      case l: java.util.ArrayList[Object] =>
        val z: List[Option[String]] = l.toList.map {
          case x: java.util.HashMap[String, String] =>
            val y: Option[String] = x.toMap.get("id")
            y.map(_.toString)
          case _ => None
        }
        Some(z.flatten)
      case _ => None
    }
  }
  partnerOpt match {
    case Some(partners) => Either.cond(partners.contains(partner), partner, "")
    case None => Left("")
  }
}

Naive rewrite of function mimicking approach in dynamic languages (non-idiomatic):

def verifyPartner(partner: String, payload: Map[String, java.lang.Object]): Either[String, String] = {
  val partners = payload.getOrElse("app-metadata", Map()).asInstanceOf[Map[String, Object]].
                         getOrElse("partners", Array()).asInstanceOf[Array[Map[String, Object]]]
  partners.find(p => p.getOrElse("id", None) == partner) match {
    case Some(partnerObject) => Right(partner)
    case None => Left("")
  }
}

Test:

val partners = Array(Map("id" -> "foo"), Map("id" -> "bar"))
val payload = Map("app-metadata" -> Map("partners" -> partners))

verifyPartner("foo", payload) // => Right(foo)
verifyPartner("foobar", payload) // => Left()

Scala Refactored (With Types)

With the help of Adam Bergmark (github.com/bergmark) I was able to refactor the Scala code by introducing proper types. First we installed the circe JSON library by adding it as a dependency in build.sbt:

val circeVersion = "0.8.0"

val dependencies = Seq(
  "io.circe"                 %% "circe-core"               % circeVersion,
  "io.circe"                 %% "circe-derivation"         % "0.8.0-M2",
  "io.circe"                 %% "circe-generic"            % circeVersion,
  "io.circe"                 %% "circe-parser"             % circeVersion
)

We introduced a data type for the JSON payload:

@JsonCodec case class JWTPartner(id: String)
@JsonCodec case class AppMetadata(partners: Array[JWTPartner], developer: Option[Boolean])
@JsonCodec case class JWTPayload(app_metadata: AppMetadata)

JSON decoding now looks like this:

val payload = parser.decode[JWTPayload](stringPayload)

With this in place the verifyPartner function is quite clean:

  def verifyPartner(partnerId: String, payload: JWTPayload): Either[String, JWTPartner] = {
    payload.app_metadata.partners.find(p => p.id == partnerId) match {
      case Some(partner) => Right(partner)
      case None => Left("Could not find partner")
    }
  }

Clojure

Function:

(defn verify-partner [partner-id payload]
  (let [payload-partners (get-in payload [:app-metadata :partners] [])
        payload-ids (set (map :id payload-partners))]
    (get payload-ids partner-id)))

Test:

(def payload {:app-metadata {:partners [{:id "foo"} {:id "bar"}]}})
(verify-partner "foo" payload) # => "foo"
(verify-partner "fooasdf" payload) # => nil
(verify-partner "fooasdf" {}) # => nil
(verify-partner "fooasdf" nil) # => nil

Python

Function:

def verifyPartner(partner_id, payload):
  payload_partners = (payload or {}).get("app-metadata", {}).get("partners", [])
  payload_ids = [p["id"] for p in payload_partners]
  return partner_id if partner_id in payload_ids else None

Test:

payload = {"app-metadata": {"partners": [{"id": "foo"}, {"id": "bar"}]}}

verifyPartner("foo", payload) # => "foo"
verifyPartner("foob", payload) # => None
verifyPartner("foob", {}) # => "foo"
verifyPartner("foob", None) # => "foo"

JavaScript

Function:

function verifyPartner(partnerId, payload) {
  const payloadMetadata = (payload || {})['app-metadata'] || {}
  const payloadPartners = payloadMetadata['partners'] || []
  const payloadIds = payloadPartners.map(p => p['id'])
  return payloadIds.includes(partnerId) ? partnerId : undefined
}

Test:

const payload = {'app-metadata': {partners: [{id: 'foo'}, {id: 'bar'}]}}
verifyPartner('foo', payload) // => 'foo'
verifyPartner('foos', payload) // => undefined
verifyPartner('foos', {}) // => undefined
verifyPartner('foos', null) // => undefined
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment