In this tutorial series, you will learn how to build an encoder to encode Scala types into Json representations. We’ll first see how to used encoders provided by Circe and in the next tutorial, we’ll build our own encoders.
- Introduction to Circe and Json Encoding
- Building an Encoder – Setup
- Building an Encoder – Build Json Values
- Building an Encoder – Manipulating Json Values
- Manipulating Nested Values
- JSON Encoding Video Tutorial 1
1. Introduction to Circe and Json Encoding
In many programming languages, JavaScript for example, converting back and forth between data structures and their JSON strings is quite easy. You could simply use the JSON.stringify() function as shown below:
// Original User object const User = { name: "Kindson", location: "Budapest", department: "Computer Science" } var userString = JSON.stringify(User) //JSON string of User object var parsedUser = JSON.parse(userString) //User object gotten from JSON string
You can run this code and use console.log() to view the variables.
In functional programming languages like Scala, things are a bit different. The process is more involved and that is why we need Circe. Circe uses type classes to describe how types in Scala should be serialized and deserialized. For instance, for the User type, we can have two type classes:
- Encoder[User] – this encoder means that a User instance can be transformed into a Json representation (io.circe.Json)
- Decoder[User] – this decoder means that Json value can be transformed into DecodedResult[A]
The figure below illustrates how serialization works in Circe

2. Encoding to Json with Circe – Setup
Let’s follow the procedure the build a decoder in Scala using Circe.
Step 1 – Create a Scala project in IntelliJ. (you can review the procedure to setup IntelliJ and create a project here. Setup Scala in IntelliJ, Write Your First Scala Program)
Step 2 – Open your build.sbt file and add the following line to add the Circe dependency to your project
libraryDependencies += "io.circe" %% "circe-core" % "0.14.1" libraryDependencies += "io.circe" %% "circe-parser" % "0.14.1" libraryDependencies += "io.circe" %% "circe-generic" % "0.14.1"
- circe-core – exposes the encoders and decoder type classes as well as Json structures and cursors that allow you to traverse and modify Json trees.
- circe-parser – allows you to parse serialized Json into abstract syntax tree (AST)
- circe-generic – allows you to derive encoders and decoders automatically using macros
Step 3 – Create an Object that extends App
3. Encoding to Json with Circe – Build JSON Values
Remember that encoder takes an instance of a data type into JSON value (io.circe.Json). So we would encode instances of the following types:
- String
- Integer
- Array
- Object
//Building Json values val jsonString = "Kindson The Genius".asJson // Json String, can be written as Json.fromString val jsonInt = Json.fromInt(43); // Json Int val jsonArray = Json.arr(jsonString, jsonInt)// Json Array val jsonObject = Json.obj( // Json Object "name" ->"Tech Pro".asJson, "title" -> "Software Engineer".asJson )
The code above shows how you can encode various data types into Json values. I have added comments to indicate the various data types.
Note: for the array types, we specify Json values as the elements. Similarly, for the object, each of the members has values that are Json values
4. Encoding to Json with Circe – Manipulating JSON Values
Json values has various methods that can be used to manipulated them. These methods are prefixed with “map”. For example:
- mapString is used to manipulate Json string
- mapArray is used to manipulate Json array
To use the mapString(), you need to call it on the Json string and pass it a function that does the transformation. In the example below, we use the function toUpperCase() to transform the given string to upper case
jsonString.mapString(_.toUpperCase())
To use the mapArray(), we call the mapArray() function, then we map through each element of the array with the map() function and for each element, we call the mapString() to transform them with the toUpperCase() function. This is shown below:
jsonArray.mapArray(_.map(_.mapString(_.toUpperCase())))
Note that this is the same as the line below, without using the _. operator.
jsonArray.mapArray(ar =>ar.map(el =>el.mapString(str => str.toUpperCase())))
5. Manipulating Nested Values
In this case, we would have an object nested inside another object. In other words, the value of the field in the Json structure is an object.
Let’s assume we have an object called personalData and inside this object, we have a field called address whose value is an object called addressObject, made up of two fields: city and street. The addressObject (encoded to Json) is given below:
val addressObj = Json.obj( "city" -> "Akocity".asJson, "street" -> 56.asJson )
With this, the personalData object with the nested addressObj is given below:
val personalData = Json.obj( "name" -> "Kindson".asJson, "address" -> addressObj )
So the goal is the manipulate the values inside the addressObj. Now we would like to manipulate this object and reverse the value of city field. We have four functions to use:
- hcursor() – used to obtain an cursor on the object
- downField() – is used to capture a child field
- withFocus() – returns an Acursor on the nested object
- top() – is used to step up to the top of the hierarchy
The code is give below
personalData.hcursor .downField("address").downField("city") .withFocus(_.mapString(_.reverse)) .top .map(_.spaces2)
The code above reverses the value of the title property and returns that Json representation of the object. I recommend you watch the video for clarification.
HCursor vs ACursor – Just like the HCursor, the ACursor also tracks the history, but and ACursor also represents the possibility of failure.
In the next part, we would actually be building decoders. I recommend you watch the video tutorial for clarification.