thoth-org/Thoth.Json

[Question] Is it possible to use already encoded objects to encode a container value

lontivero opened this issue · 2 comments

Note: if this is not the correct place for this please let me know where I can get help. Thanks.

I need to serialize an array and the expected result is this:

["EVENT", "subcription-12345", {"property": "value"}]
The third element is already serialized, what means that I already have the string "{"property": "value"}" that I get from a database. All this means that I could create this by simply concatenating strings but it feels wrong.

If I do this the result is :

["EVENT", "subcription-12345", "{\"property\": \"value\"}"]

type SubscriptionId = string
type SerializedEvent = string
type RelayMessage = 
     | RMEvent of SubscriptionId * SerializedEvent

let encode = function
    | RMEvent (subscriptionId, serializedEvent) ->
        Encode.tuple3
            Encode.string
            Encode.string
            Encode.string
            ("EVENT", subscriptionId, serializedEvent)

let serializedMessage =
    RMEvent ("subscription-1234", """{"property":"value"}""")
    |> encode
    |> Encode.toString 0

Demo in fable.io/

Is there any way to do what I need?

Hello,

From a JSON point of view, if you have a serialised value and you try to embed it into a JSON it is the same as if you have a string.

Indeed, the serialised value is just a string and is handle as it.

In JavaScript code, what you do is equivalent to:

var embedJson = '{"property":"value"}';

JSON.stringify({ event: embedJson})
// '{"event":"{\\"property\\":\\"value\\"}"}'

If you want to avoid the double serialisation, you need to deserialise the embedJson so it can be serialise only once.

var embedJson = '{"property":"value"}';
var deserialisedJson = JSON.parse(embedJson);
JSON.stringify({ event: deserialisedJson})
// '{"event":{"property":"value"}}'

You can do that in Thoth.Json by adding a custom Encode function:

module Encode =

    let passthrough value =
        JS.JSON.parse value

let encode = function
    | RMEvent (subscriptionId, serializedEvent) ->
        Encode.tuple3
            Encode.string
            Encode.string
            Encode.passthrough
            ("EVENT", subscriptionId, serializedEvent)

Demo link

However, if you do such a thing it means that the JSON you get as an output is not respecting the Domain you have in F#.

Your domain say:

type SubscriptionId = string
type SerializedEvent = string
type RelayMessage = 
     | RMEvent of SubscriptionId * SerializedEvent

but your JSON output map to:

type SubscriptionId = string
type SerializedEvent = {| property: string |} //Using anonymous record for simplicity
type RelayMessage = 
     | RMEvent of SubscriptionId * SerializedEvent

Personally, I would not use the passthrough encoder that I showed you because it doesn't feel like a robust solution to me. And just postponing the problem.

Depending on your use case, I would probably keep the serialised event as it is and decode it later when needed. Or if having a clean JSON is important, then I would probably rework the domain to use something like:

type SubscriptionId = string
type SerializedEvent = 
	| Event1 of ...
	| Event2 of ...
	| Event3 of ...
type RelayMessage = 
     | RMEvent of SubscriptionId * SerializedEvent

in order to have full control over the JSON and have the domain and JSON representation aligned.

I appreciate very much the quality of your response. If well the project I am working on is simply a toy to learn F#, I am playing with some optimizations (early ones) that no other projects seem to have consider, like avoiding the re-serialization of events (json messages). In this case I would go with the simplest solution that is concatenate strings on the relay side.

All the rest will use Thoth.Json. I love this project, really.