Skip to content

Derive codec for parent sealed trait, given codecs for children #1269

@kamilkloch

Description

@kamilkloch

Alternatively: provide means to chain codecs (e.g. as fallbackTo).

Imagine a following use-case: an API where codecs for the entire ADT cannot be derived automatically (e.g., due to inconsistent discriminator policy of the API). Concrete example: Kraken websocket API. Two top-level discriminators: method or channel, result containing an optional channel field (which only then is to be treated as a discriminator for a subtree of messages):

https://docs.kraken.com/api/docs/websocket-v2/book#subscribe-request

{
    "method": "subscribe",
    "result": {
        "channel": "book",
        "depth": 10,
        "snapshot": true,
        "symbol": "ALGO/USD"
    },
    "success": true,
    "time_in": "2023-10-06T17:35:55.219022Z",
    "time_out": "2023-10-06T17:35:55.219067Z"
}

https://docs.kraken.com/api/docs/websocket-v2/trade#subscribe-request

{
    "method": "subscribe",
    "result": {
        "channel": "trade",
        "snapshot": true,
        "symbol": "MATIC/USD"
    },
    "success": true,
    "time_in": "2023-09-25T09:21:10.428340Z",
    "time_out": "2023-09-25T09:21:10.428375Z"
}

https://docs.kraken.com/api/docs/websocket-v2/add_order#response

{
    "method": "add_order",
    "req_id": 123456789,
    "result": {
        "order_id": "AA5JGQ-SBMRC-SCJ7J7",
        "order_userref": 100054
    },
    "success": true,
    "time_in": "2023-09-21T14:15:07.197274Z",
    "time_out": "2023-09-21T14:15:07.205301Z"
}

https://docs.kraken.com/api/docs/websocket-v2/trade#snapshot-and-update-response

{
    "channel": "trade",
    "type": "snapshot",
    "data": [
        {
            "symbol": "MATIC/USD",
            "side": "buy",
            "price": 0.5147,
            "qty": 6423.46326,
            "ord_type": "limit",
            "trade_id": 4665846,
            "timestamp": "2023-09-25T07:48:36.925533Z"
        },
        {
            "symbol": "MATIC/USD",
            "side": "buy",
            "price": 0.5147,
            "qty": 1136.19677815,
            "ord_type": "limit",
            "trade_id": 4665847,
            "timestamp": "2023-09-25T07:49:36.925603Z"
        }
    ]
}

In short, a standard automatic top-down approach of deriving codecs (start with a top sealed trait, use discriminators at each nested level to find a first match of a subtype) fails short here.

A working solution: derive codecs for each message type separately (via JsonCodecMaker.make), then manually define codec for the parent sealed trait by combining codecs of the children with the firstOf policy. (see https://circe.github.io/circe/codecs/adt.html).

This is approach is currently difficult to implement, as the parent codec must actually dwell into the internals of child messages (see e948fb9, relates to #1266). Ideally, we would like to be able to combine child codecs as black boxes, without manually applying multiple low-level discriminating steps in the parent codec.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions