or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

builtin-types.mdcore-serialization.mdindex.mdjson-integration.mdmessagepack-integration.mdsealed-traits.mdstreaming.mdtype-classes.md

sealed-traits.mddocs/

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

```