Skip to content

WithUnionTagNamingPolicy is ignored if UnionTagName is set #216

Description

@Bananas-Are-Yellow

Some sample types

type PieceOfText = { Text: string }
type LengthOfPipe = { Length: int }
type Junk =
  | PieceOfText of PieceOfText
  | LengthOfPipe of LengthOfPipe

Set up some options so that union tag names and record field names use snake_lower_case.

let makeOptions namingPolicy =
    JsonFSharpOptions.Default()
        .WithUnionTagNamingPolicy(namingPolicy)
        .WithUnionInternalTag()
        .WithUnionUnwrapRecordCases()
        .WithAllowOverride()
        .ToJsonSerializerOptions(PropertyNamingPolicy = namingPolicy)

let options = makeOptions JsonNamingPolicy.SnakeCaseLower

Try out some serialization

let value = PieceOfText { Text = "Hello" }

let json = JsonSerializer.Serialize (value, options)
val json: string = "{"Case":"piece_of_text","text":"Hello"}"

So far, so good.

Now use [<JsonFSharpConverter>] to specify the tag name:

[<JsonFSharpConverter (UnionTagName = "type")>]
type Junk =
  | PieceOfText of PieceOfText
  | LengthOfPipe of LengthOfPipe

let value = PieceOfText { Text = "Hello" }

let json = JsonSerializer.Serialize (value, options)
val json: string = "{"type":"PieceOfText","text":"Hello"}"

The union tag value is no longer converted to snake_lower_case.

OK, let's try a workaround, which is to set UnionTagNamingPolicy on the attribute.

[<JsonFSharpConverter (UnionTagName = "type", UnionTagNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)>]
type Junk =
  | PieceOfText of PieceOfText
  | LengthOfPipe of LengthOfPipe

let value = PieceOfText { Text = "Hello" }

let json = JsonSerializer.Serialize (value, options)

System.Reflection.CustomAttributeFormatException: 'UnionTagNamingPolicy' property specified was not found.
 ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.Exception: Unknown naming policy: SnakeCaseLower
   at Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThenFail@1448.Invoke(String message)
   at System.Text.Json.Serialization.JsonFSharpConverterAttribute.set_UnionTagNamingPolicy(JsonKnownNamingPolicy v) in /home/runner/work/FSharp.SystemTextJson/FSharp.SystemTextJson/src/FSharp.SystemTextJson/All.fs:line 108
   at InvokeStub_JsonFSharpConverterAttribute.set_UnionTagNamingPolicy(Object, Span`1)
   at System.Reflection.RuntimeMethodInfo.InvokePropertySetter(Object obj, BindingFlags invokeAttr, Binder binder, Object parameter, CultureInfo culture)
   --- End of inner exception stack trace ---
   at System.Reflection.RuntimeMethodInfo.InvokePropertySetter(Object obj, BindingFlags invokeAttr, Binder binder, Object parameter, CultureInfo culture)
   at System.Reflection.CustomAttribute.AddCustomAttributes(ListBuilder`1& attributes, RuntimeModule decoratedModule, Int32 decoratedMetadataToken, RuntimeType attributeFilterType, Boolean mustBeInheritable, ListBuilder`1 derivedAttributes)
   --- End of inner exception stack trace ---
   at System.Reflection.CustomAttribute.AddCustomAttributes(ListBuilder`1& attributes, RuntimeModule decoratedModule, Int32 decoratedMetadataToken, RuntimeType attributeFilterType, Boolean mustBeInheritable, ListBuilder`1 derivedAttributes)
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeType type, RuntimeType caType, Boolean inherit)
   at System.Text.Json.Serialization.Helpers.overrideOptions(Type ty, JsonFSharpOptions defaultOptions) in /home/runner/work/FSharp.SystemTextJson/FSharp.SystemTextJson/src/FSharp.SystemTextJson/Helpers.fs:line 143
   at System.Text.Json.Serialization.JsonUnionConverter.CreateConverter(Type typeToConvert, JsonSerializerOptions options, JsonFSharpOptions fsOptions) in /home/runner/work/FSharp.SystemTextJson/FSharp.SystemTextJson/src/FSharp.SystemTextJson/Union.fs:line 846
   at System.Text.Json.Serialization.JsonConverterFactory.GetConverterInternal(Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializerOptions.ExpandConverterFactory(JsonConverter converter, Type typeToConvert)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.GetConverterForType(Type typeToConvert, JsonSerializerOptions options, Boolean resolveJsonConverterAttribute)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.GetTypeInfo(Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoNoCaching(Type type)
   at System.Text.Json.JsonSerializerOptions.CachingContext.CreateCacheEntry(Type type, CachingContext context)
--- End of stack trace from previous location ---
   at System.Text.Json.JsonSerializerOptions.CachingContext.CacheEntry.GetResult()
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoForRootType(Type type, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.JsonSerializer.GetTypeInfo[T](JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Serialize[TValue](TValue value, JsonSerializerOptions options)
   at <StartupCode$FSI_0041>.$FSI_0041.main@() in /home/david/code/EditorPlay/stdin:line 3
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig, BOOL isConstructor, ObjectHandleOnStack result)
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig, BOOL isConstructor, ObjectHandleOnStack result)
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
Stopped due to error

The exception comes from here:

    static let namingPolicy =
        function
        | JsonKnownNamingPolicy.Unspecified -> null
        | JsonKnownNamingPolicy.CamelCase -> JsonNamingPolicy.CamelCase
        | p -> failwithf "Unknown naming policy: %A" p

If I use UnionTagNamingPolicy = JsonKnownNamingPolicy.CamelCase, the exception does not occur, but that's not the naming policy I want.

One workaround is to use [<JsonName>] on each union case to explicitly set the snake_lower_case result I want

[<JsonFSharpConverter (UnionTagName = "type")>]
type Junk =
  | [<JsonName "piece_of_text">] PieceOfText of PieceOfText
  | [<JsonName "length_of_pipe">] LengthOfPipe of LengthOfPipe

let value = PieceOfText { Text = "Hello" }

let json = JsonSerializer.Serialize (value, options)
val json: string = "{"type":"piece_of_text","text":"Hello"}"

This works, but it's a bit tedious and error prone.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions