Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Types with mutual recursion are not supported #155

Open
jmfallecker opened this issue Mar 24, 2023 · 2 comments
Open

Types with mutual recursion are not supported #155

jmfallecker opened this issue Mar 24, 2023 · 2 comments

Comments

@jmfallecker
Copy link

Discriminated unions that are self-referential and any mutually recursive types are not supported. Using the $id and $ref keys in JSON should allow this type of structure to be serialized.

for a type definition like such:

type A = { B of B } and B = { A of A }

the json should end up being something like:

{
"A": {
"$id": "1",
"B": { "$ref": "2" }
},
"B": {
"$id": "2",
"A": { "$ref": "1" }
}
}

This is an incredibly simplified example, but there's an approach we can take using reflection to figure out what properties cause a cycle in a generic data structure. Once we've identified the cycle, we can use comparison of all other fields (excluding the cyclical part) to understand which records reference which other records.

I've included a first draft of a function to determine if a type is recursive or not.

isRecursive.txt

@Tarmil
Copy link
Owner

Tarmil commented Mar 27, 2023

I would be curious to see your actual use case in more detail. Mutually recursive types work fine, for example:

type A =
  { b: B option }
and B =
  { a: A }

let x = { b = Some { a = { b = None } } }

JsonSerializer.Serialize(x, JsonFSharpOptions().ToJsonSerializerOptions())
// --> {"b":{"a":{"b":null}}}

However, self-recursive values are indeed not supported. System.Text.Json has ReferenceHandler.Preserve that allows producing the kind of JSON you list, but its documentation indicates:

This feature can't be used to preserve value types or immutable types. On deserialization, the instance of an immutable type is created after the entire payload is read. So it would be impossible to deserialize the same instance if a reference to it appears within the JSON payload.

The same applies to the way FSharp.SystemTextJson deserializes records and unions: the returned value is created after reading the payload, so it can't be referenced from inside the payload.

@jmfallecker
Copy link
Author

jmfallecker commented Oct 30, 2023

My main use case was within a bolero application. I was attempting to serialize a model to be able to pass it to an API, then deserialize the same model on the server side.

It works fine for non recursive types, like you said.

Sometimes when domain modeling, a recursive type just makes things clearer, so I'd hate to have to avoid them.

I am responding quite a bit later than when I originally posted this, but I'll check out the code again and see what you're talking about with the order of events.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants