title | type | description | num | previous-page | next-page |
---|---|---|---|---|---|
How to deserialize JSON to an object? | section | Parsing JSON to a custom data type | 19 | json-modify | json-serialize |
{% include markdown.html path="_markdown/install-upickle.md" %}
Parsing with uJson only accepts valid JSON, but it does not validate that the names and types of fields are as expected.
Deserialization, on the other hand, transforms a JSON string to some user-specified Scala data type, if required fields are present and have the correct types.
In this tutorial, we show how to deserialize to a Map
and also to a custom case class
.
For a type T
, uPickle can deserialize JSON to a Map[String, T]
, checking that all fields conform to T
.
We can for instance, deserialize to a Map[String, List[Int]]
:
{% tabs 'parsemap' %} {% tab 'Scala 2 and 3' %}
valjson="""{"primes": [2, 3, 5], "evens": [2, 4, 6]} """valmap:Map[String, List[Int]] = upickle.default.read[Map[String, List[Int]]](json) println(map("primes")) // prints: List(2, 3, 5)
{% endtab %} {% endtabs %}
If a value is the wrong type, uPickle throws a upickle.core.AbortException
.
{% tabs 'parsemap-error' %} {% tab 'Scala 2 and 3' %}
valjson="""{"name": "Peter"} """ upickle.default.read[Map[String, List[Int]]](json) // throws: upickle.core.AbortException: expected sequence got string at index 9
{% endtab %} {% endtabs %}
In Scala, you can use a case class
to define your own data type. For example, to represent a pet owner, you might:
caseclassPetOwner(name: String, pets: List[String])
To read a PetOwner
from JSON, we must provide a ReadWriter[PetOwner]
. uPickle can do that automatically:
{% tabs 'given' class=tabs-scala-version %} {% tab 'Scala 2' %}
importupickle.default._implicitvalownerRw:ReadWriter[PetOwner] = macroRW[PetOwner]
Some explanations:
- An
implicit val
is a value that can be automatically provided as an argument to a method or function call, without having to explicitly pass it. macroRW
is a method provided by uPickle that can generate a instances ofReadWriter
for case classes, using the information about its fields. {% endtab %} {% tab 'Scala 3' %}
importupickle.default.*caseclassPetOwner(name: String, pets: List[String]) derivesReadWriter
The derives
keyword is used to automatically generate given instances. Using the compiler's knowledge of the fields in PetOwner
, it generates a ReadWriter[PetOwner]
. {% endtab %} {% endtabs %}
This means that you can now read (and write) PetOwner
objects from JSON with upickle.default.read(petOwner)
.
Notice that you do not need to pass the instance of ReadWriter[PetOwner]
explicitly to the read
method. But it does, nevertheless, get it from the context, as "given" value. You may find more information about contextual abstractions in the Scala 3 Book.
Putting everything together you should get:
{% tabs 'full' class=tabs-scala-version %} {% tab 'Scala 2' %}
importupickle.default._caseclassPetOwner(name: String, pets: List[String]) implicitvalownerRw:ReadWriter[PetOwner] = macroRW valjson="""{"name": "Peter", "pets": ["Toolkitty", "Scaniel"]}"""valpetOwner:PetOwner= read[PetOwner](json) valfirstPet= petOwner.pets.head println(s"${petOwner.name} has a pet called $firstPet") // prints: Peter has a pet called Toolkitty
{% endtab %} {% tab 'Scala 3' %}
importupickle.default.*caseclassPetOwner(name: String, pets: List[String]) derivesReadWritervaljson="""{"name": "Peter", "pets": ["Toolkitty", "Scaniel"]}"""valpetOwner:PetOwner= read[PetOwner](json) valfirstPet= petOwner.pets.head println(s"${petOwner.name} has a pet called $firstPet") // prints: Peter has a pet called Toolkitty
{% endtab %} {% endtabs %}