[Blog Post] Crystal JSON beyond the basics

Hi Crystal folks, I’ve just published an article on how to use the json package for encoding and decoding algebraic data types.

This covers

  • Automatic encoding with JSON::Serializable
  • Type resolution with discriminators
  • Encoding of nested composite data types
  • Considerations on the extensibility of this approach

Let me know how you find this, and tell your friends!

14 Likes

Really nice article!

How do you handle each event later on? Using a case, or method overloading, or something else?

I ask because a type hierarchy is not quite an algebraic data type. The reason is that in other languages you can pattern match against an algebraic type and make sure you cover all cases. Because inheritance is open you can’t do that in Crystal: someone might extend Event and add yet another Event that you didn’t think about.

I’d really like to add a Sealed attribute similar to what you can do in Sorbet, Kotlin and I think other languages, but others don’t seem to like or understand its usefulness.

4 Likes

Yes, I use a case (here is the actual code), something like:

  case event
  when Initialized
    total = event.total
    todo = event.todo
    name = event.name
  when Connected
    peer_data[event.peer] = PeerStatus.new(event.peer.address, :connected)
  end

I ask because a type hierarchy is not quite an algebraic data type. The reason is that in other languages you can pattern match against an algebraic type and make sure you cover all cases

I see what you mean. Would switching to an exhaustive case make this feel more like an ADT? Or would we still missing out on some properties?

I’d really like to add a Sealed attribute similar to what you can do in Sorbet, Kotlin and I think other languages, but others don’t seem to like or understand its usefulness.

I come from Scala’s sealed traits, so you have my full support on this :slight_smile:

2 Likes

Would switching to an exhaustive case make this feel more like an ADT?

Yes!

1 Like

Could exhaustive case exist at the language level? Like a modifier over case?

I understand introducing keywords are frowned upon so maybe final could have some meaning there. Or an entirely new feature with a more general syntax for extending some constructs with modifiers/attributes:

case(exhaustive, other_modifier) event

case ::exhaustive, other_modifier:: event

Thoughts?

It already exists: https://github.com/crystal-lang/crystal/pull/9258

It’s not documented yet.

1 Like

Was it ever considered to replace the case keyword instead of when for exhaustiveness checking? For example,

match foo
when Bar
  ...
end

It’s a minor thing, but it would require a single change if you want to enable/disable exhaustiveness checks, instead of a change for each branch.

1 Like

Yes, it was considered. The match proposal is a breaking change, case in is not.

1 Like

While I appreciate the availability of the exhaustive case, even undocumented, I think there is a case to be made for attributes/modifiers in general. Even if I don’t have handy examples right now, I figure an extensibility mechanism is itself laudable and use cases will pop left and right the moment such a functionality is made available. That, along with the macro system, should enable some wicked use cases. Not all attributes have to be built in as long as there are ways to create those behaviour wrappers.

And… Aspects. Cross-cutting concerns are a thing. While I’m not fond of pointcut expressions, I think I can live with attributes to import aspects.

1 Like