Tarmil / FSharp.SystemTextJson

System.Text.Json extensions for F# types
MIT License
323 stars 44 forks source link

Unwrap the whole union into a record #157

Open shanoaice opened 1 year ago

shanoaice commented 1 year ago

I have the following two types:

[<JsonFSharpConverter(UnionTagName = "tag")>]
type ExUnion = 
| CaseOne of string
| CaseTwo of int

type ExRecord =
{ settings: ExUnion 
  // and some other fields that are the same for all cases of settings, which will be unwise to pack them into ExUnion
   }

let exVar = { settings = CaseOne "example" }

and I want the exVar to be serialized as follows, where the record field name indicates the field name of the case value, and another field that indicates the tag of the union:

{
    "tag": "CaseOne",
    "settings": "example"
}

with JsonUnionEncoding.AdjacentTag I was only able to get the follows:

{
    "settings": {
        "Case": "CaseOne",
        "Fields": "example"
    }
}

And I can't figure out how to achieve what I want.

EDIT: I figured out how to do that:

type ExRecord<'a> =
    { settings: 'a }

[<JsonFSharpConverter(BaseUnionEncoding = JsonUnionEncoding.InternalTag, UnionUnwrapRecordCases = true, UnionTagName = "tag")]
type ExUnion = 
    | Case1 of ExRecord<string>
    | Case2 of ExRecord<int>

and call JsonSerializer.Serialize() on instances of ExUnion instead of ExRecord. But I still feel that such pattern is a bit counterintuitive, since I want to represent a field in a record whose type is annotated by another field in the same record, on the same level. Wrapping that with a DU under the record feels like a more natural way to do that. Even worse, this reverse-wrapping won't work if I want to achieve the follows:

[<JsonFSharpConverter(UnionTagName = "tag")>]
type ExUnion1 = 
| CaseOne of string
| CaseTwo of int

[<JsonFSharpConverter(UnionTagName = "tag2")>]
type ExUnion2 =
| A of int
| B of string

type ExRecord =
{ settings: ExUnion1
  settings2: ExUnion2 }

let exVar = { settings = CaseOne "example"; settings2 = A 8 }

and serialize exVar into the follows:

{
    "tag": "CaseOne",
    "settings": "example",
    "tag2": "A",
    "settings2": 8
}
shanoaice commented 10 months ago

Hello, is there any ways to achieve that without using obj and downcast?