0
# Sealed Traits
1
2
Automatic tagging and discrimination for sealed trait hierarchies with customizable behavior. uPickle provides sophisticated support for sealed trait serialization with multiple tagging strategies.
3
4
## Capabilities
5
6
### Automatic Sealed Trait Derivation
7
8
Automatic Reader/Writer generation for sealed traits with tagged discrimination.
9
10
**Usage Examples:**
11
12
```scala
13
import upickle.default._
14
15
// Basic sealed trait hierarchy
16
sealed trait Animal
17
case class Dog(name: String, breed: String) extends Animal
18
case class Cat(name: String, indoor: Boolean) extends Animal
19
case class Bird(name: String, species: String) extends Animal
20
21
// Automatic derivation
22
implicit val animalRW: ReadWriter[Animal] = ReadWriter.merge(
23
macroRW[Dog], macroRW[Cat], macroRW[Bird]
24
)
25
26
val animals = List[Animal](
27
Dog("Buddy", "Golden Retriever"),
28
Cat("Whiskers", true),
29
Bird("Tweety", "Canary")
30
)
31
32
// Serialization with automatic tagging
33
val json = write(animals)
34
println(json)
35
// Result: [
36
// {"$type":"Dog","name":"Buddy","breed":"Golden Retriever"},
37
// {"$type":"Cat","name":"Whiskers","indoor":true},
38
// {"$type":"Bird","name":"Tweety","species":"Canary"}
39
// ]
40
41
// Deserialization with automatic discrimination
42
val parsed = read[List[Animal]](json)
43
```
44
45
### TaggedReader
46
47
Reader with tagging support for sealed trait discrimination during deserialization.
48
49
```scala { .api }
50
/**
51
* Reader with tagging support for sealed traits
52
*/
53
trait TaggedReader[T] extends SimpleReader[T] {
54
private[upickle] def tagKey: String
55
def findReader(s: String): Reader[T]
56
}
57
58
object TaggedReader {
59
/**
60
* Leaf reader for a specific tagged type
61
*/
62
class Leaf[T](tagKey: String, tagValue: String, shortValue: String, r: Reader[T]) extends TaggedReader[T]
63
64
/**
65
* Node reader that delegates to child readers based on tag
66
*/
67
class Node[T](tagKey: String, rs: TaggedReader[_ <: T]*) extends TaggedReader[T]
68
}
69
```
70
71
**Usage Examples:**
72
73
```scala
74
import upickle.default._
75
import upickle.core._
76
77
sealed trait Status
78
case object Active extends Status
79
case object Inactive extends Status
80
case object Pending extends Status
81
82
// Manual tagged reader construction
83
val statusReader = new TaggedReader.Node[Status](
84
"$type",
85
new TaggedReader.Leaf("$type", "Active", "Active", macroR[Active.type]),
86
new TaggedReader.Leaf("$type", "Inactive", "Inactive", macroR[Inactive.type]),
87
new TaggedReader.Leaf("$type", "Pending", "Pending", macroR[Pending.type])
88
)
89
90
// Usage
91
val json = """{"$type":"Active"}"""
92
val status = statusReader.transform(ujson.parse(json))
93
```
94
95
### TaggedWriter
96
97
Writer with tagging support for sealed trait discrimination during serialization.
98
99
```scala { .api }
100
/**
101
* Writer with tagging support for sealed traits
102
*/
103
trait TaggedWriter[T] extends Writer[T] {
104
def findWriterWithKey(v: Any): (String, String, ObjectWriter[T])
105
}
106
107
object TaggedWriter {
108
/**
109
* Leaf writer for a specific tagged type
110
*/
111
class Leaf[T](checker: Annotator.Checker, tagKey: String, tagValue: String, r: ObjectWriter[T]) extends TaggedWriter[T]
112
113
/**
114
* Node writer that delegates to child writers based on runtime type
115
*/
116
class Node[T](rs: TaggedWriter[_ <: T]*) extends TaggedWriter[T]
117
}
118
```
119
120
### TaggedReadWriter
121
122
Combined tagged reader and writer for bidirectional sealed trait handling.
123
124
```scala { .api }
125
/**
126
* Combined tagged reader and writer
127
*/
128
trait TaggedReadWriter[T] extends ReadWriter[T] with TaggedReader[T] with TaggedWriter[T]
129
130
object TaggedReadWriter {
131
/**
132
* Leaf readwriter for a specific tagged type
133
*/
134
class Leaf[T](c: ClassTag[_], tagKey: String, tagValue: String, r: ObjectWriter[T] with Reader[T]) extends TaggedReadWriter[T]
135
136
/**
137
* Node readwriter that delegates based on tag/type
138
*/
139
class Node[T](tagKey: String, rs: TaggedReadWriter[_ <: T]*) extends TaggedReadWriter[T]
140
}
141
```
142
143
### Default Tagging (AttributeTagged)
144
145
Default tagging strategy using JSON object attributes.
146
147
**Usage Examples:**
148
149
```scala
150
import upickle.default._ // Uses AttributeTagged
151
152
sealed trait Shape
153
case class Circle(radius: Double) extends Shape
154
case class Rectangle(width: Double, height: Double) extends Shape
155
case class Triangle(base: Double, height: Double) extends Shape
156
157
implicit val shapeRW: ReadWriter[Shape] = macroRW
158
159
val shapes = List[Shape](
160
Circle(5.0),
161
Rectangle(10.0, 8.0),
162
Triangle(6.0, 4.0)
163
)
164
165
val json = write(shapes)
166
println(json)
167
// Result: [
168
// {"$type":"Circle","radius":5.0},
169
// {"$type":"Rectangle","width":10.0,"height":8.0},
170
// {"$type":"Triangle","base":6.0,"height":4.0}
171
// ]
172
173
// Custom tag key
174
object CustomApi extends upickle.AttributeTagged {
175
override def tagName = "kind"
176
}
177
178
import CustomApi._
179
val customJson = write(shapes)
180
// Result uses "kind" instead of "$type"
181
```
182
183
### Legacy Tagging
184
185
Legacy tagging strategy using JSON arrays with type/value pairs.
186
187
**Usage Examples:**
188
189
```scala
190
import upickle.legacy._ // Uses LegacyApi
191
192
sealed trait Result[T]
193
case class Success[T](value: T) extends Result[T]
194
case class Error[T](message: String) extends Result[T]
195
196
implicit def resultRW[T: ReadWriter]: ReadWriter[Result[T]] = macroRW
197
198
val results = List[Result[String]](
199
Success("Hello"),
200
Error("Failed")
201
)
202
203
val json = write(results)
204
println(json)
205
// Result: [
206
// ["Success", {"value":"Hello"}],
207
// ["Error", {"message":"Failed"}]
208
// ]
209
210
val parsed = read[List[Result[String]]](json)
211
```
212
213
### Custom Tag Keys
214
215
Customizing tag keys for sealed trait discrimination.
216
217
**Usage Examples:**
218
219
```scala
220
import upickle.default._
221
222
sealed trait EventType
223
case class UserLogin(userId: String, timestamp: Long) extends EventType
224
case class UserLogout(userId: String, timestamp: Long) extends EventType
225
case class DataUpdated(table: String, recordId: String) extends EventType
226
227
// Custom API with different tag key
228
object EventApi extends upickle.AttributeTagged {
229
override def tagName = "eventType"
230
}
231
232
import EventApi._
233
234
implicit val eventTypeRW: ReadWriter[EventType] = macroRW
235
236
val events = List[EventType](
237
UserLogin("user123", System.currentTimeMillis()),
238
DataUpdated("users", "456")
239
)
240
241
val json = write(events)
242
// Result uses "eventType" as tag key instead of "$type"
243
244
// Multiple tag key inheritance
245
trait BaseEvent
246
sealed trait UserEvent extends BaseEvent
247
sealed trait SystemEvent extends BaseEvent
248
249
case class Login(user: String) extends UserEvent
250
case class Logout(user: String) extends UserEvent
251
case class Restart(reason: String) extends SystemEvent
252
253
implicit val baseEventRW: ReadWriter[BaseEvent] = ReadWriter.merge(
254
"type", // Custom tag key
255
ReadWriter.merge[UserEvent](macroRW[Login], macroRW[Logout]),
256
ReadWriter.merge[SystemEvent](macroRW[Restart])
257
)
258
```
259
260
### Singleton Object Handling
261
262
Special handling for case objects and singleton values.
263
264
**Usage Examples:**
265
266
```scala
267
import upickle.default._
268
269
sealed trait Priority
270
case object Low extends Priority
271
case object Medium extends Priority
272
case object High extends Priority
273
case object Critical extends Priority
274
275
implicit val priorityRW: ReadWriter[Priority] = macroRW
276
277
// Singleton objects serialize to just the tag
278
val priority = High
279
val json = write(priority)
280
// Result: "High" (string, not object)
281
282
val parsed = read[Priority](json)
283
// Result: High
284
285
// Mixed singleton and case class hierarchy
286
sealed trait Command
287
case object Start extends Command
288
case object Stop extends Command
289
case class Configure(settings: Map[String, String]) extends Command
290
291
implicit val commandRW: ReadWriter[Command] = macroRW
292
293
val commands = List[Command](Start, Configure(Map("debug" -> "true")), Stop)
294
val json = write(commands)
295
// Result: ["Start", {"$type":"Configure","settings":{"debug":"true"}}, "Stop"]
296
```
297
298
### Custom Discriminator Logic
299
300
Advanced patterns for custom discrimination logic.
301
302
**Usage Examples:**
303
304
```scala
305
import upickle.default._
306
import upickle.core._
307
308
// Custom discriminator based on content rather than explicit tag
309
sealed trait Message
310
case class TextMessage(content: String) extends Message
311
case class ImageMessage(url: String, width: Int, height: Int) extends Message
312
case class VideoMessage(url: String, duration: Int) extends Message
313
314
// Custom reader that discriminates based on field presence
315
implicit val messageReader: Reader[Message] = new TaggedReader[Message] {
316
override def tagKey = "$type"
317
318
def findReader(s: String): Reader[Message] = s match {
319
case "text" => macroR[TextMessage]
320
case "image" => macroR[ImageMessage]
321
case "video" => macroR[VideoMessage]
322
case _ => null
323
}
324
325
override def visitObject(length: Int, jsonableKeys: Boolean, index: Int) = {
326
// Custom logic to determine type from content
327
new ObjVisitor[Any, Message] {
328
var fields = Map.empty[String, ujson.Value]
329
330
def visitKey(index: Int) = upickle.core.StringVisitor
331
def visitKeyValue(s: Any): Unit = {}
332
def subVisitor = ujson.Value
333
334
def visitValue(v: Any, index: Int): Unit = {
335
// Store field for analysis
336
}
337
338
def visitEnd(index: Int): Message = {
339
// Analyze fields to determine type
340
if (fields.contains("content") && fields.size == 1) {
341
macroR[TextMessage].visitObject(length, jsonableKeys, index).visitEnd(index)
342
} else if (fields.contains("width") && fields.contains("height")) {
343
macroR[ImageMessage].visitObject(length, jsonableKeys, index).visitEnd(index)
344
} else {
345
macroR[VideoMessage].visitObject(length, jsonableKeys, index).visitEnd(index)
346
}
347
}
348
}
349
}
350
}
351
352
// Custom writer with content-based discrimination
353
implicit val messageWriter: Writer[Message] = new TaggedWriter[Message] {
354
def findWriterWithKey(v: Any): (String, String, ObjectWriter[Message]) = v match {
355
case _: TextMessage => ("$type", "text", macroW[TextMessage].asInstanceOf[ObjectWriter[Message]])
356
case _: ImageMessage => ("$type", "image", macroW[ImageMessage].asInstanceOf[ObjectWriter[Message]])
357
case _: VideoMessage => ("$type", "video", macroW[VideoMessage].asInstanceOf[ObjectWriter[Message]])
358
}
359
}
360
```
361
362
## Types
363
364
```scala { .api }
365
/**
366
* Configuration for object type key handling
367
*/
368
trait Config {
369
def tagName: String = "$type"
370
def objectTypeKeyReadMap: Map[String, String] = Map.empty
371
def objectTypeKeyWriteMap: Map[String, String] = Map.empty
372
def objectTypeKeyWriteFullyQualified: Boolean = false
373
}
374
375
/**
376
* Annotation support for tagged serialization
377
*/
378
trait Annotator {
379
def annotate[V](rw: Reader[V], key: String, value: String, shortValue: String): TaggedReader[V]
380
def annotate[V](rw: ObjectWriter[V], key: String, value: String, shortValue: String, checker: Annotator.Checker): TaggedWriter[V]
381
}
382
383
object Annotator {
384
def defaultTagKey: String = "$type"
385
386
sealed trait Checker
387
object Checker {
388
case class Cls(c: Class[_]) extends Checker
389
case class Val(v: Any) extends Checker
390
}
391
}
392
```