Skip to content

Commit

Permalink
Add compile time switch to alter encoder enum representation
Browse files Browse the repository at this point in the history
  • Loading branch information
jangko committed Jul 26, 2024
1 parent 6ff8076 commit c539f41
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 4 deletions.
27 changes: 27 additions & 0 deletions json_serialization/format.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,31 @@ template supports*(_: type Json, T: type): bool =
# The JSON format should support every type
true

type
EnumRepresentation* = enum
EnumAsString
EnumAsNumber
EnumAsStringifiedNumber

template flavorUsesAutomaticObjectSerialization*(T: type DefaultFlavor): bool = true
template flavorOmitsOptionalFields*(T: type DefaultFlavor): bool = true
template flavorRequiresAllFields*(T: type DefaultFlavor): bool = false
template flavorAllowsUnknownFields*(T: type DefaultFlavor): bool = false
template flavorSkipNullFields*(T: type DefaultFlavor): bool = false

var DefaultFlavorEnumRep {.compileTime.} = EnumAsString
template flavorEnumRep*(T: type DefaultFlavor): EnumRepresentation =
DefaultFlavorEnumRep

template flavorEnumRep*(T: type DefaultFlavor, rep: static[EnumRepresentation]) =
static:
DefaultFlavorEnumRep = rep

# If user choose to use `Json` instead of `DefaultFlavor`, it still goes to `DefaultFlavor`
template flavorEnumRep*(T: type Json, rep: static[EnumRepresentation]) =
static:
DefaultFlavorEnumRep = rep

# We create overloads of these traits to force the mixin treatment of the symbols
type DummyFlavor* = object
template flavorUsesAutomaticObjectSerialization*(T: type DummyFlavor): bool = true
Expand Down Expand Up @@ -53,3 +72,11 @@ template createJsonFlavor*(FlavorName: untyped,
template flavorRequiresAllFields*(T: type FlavorName): bool = requireAllFields
template flavorAllowsUnknownFields*(T: type FlavorName): bool = allowUnknownFields
template flavorSkipNullFields*(T: type FlavorName): bool = skipNullFields

var `FlavorName EnumRep` {.compileTime.} = EnumAsString
template flavorEnumRep*(T: type FlavorName): EnumRepresentation =
`FlavorName EnumRep`

template flavorEnumRep*(T: type FlavorName, rep: static[EnumRepresentation]) =
static:
`FlavorName EnumRep` = rep
20 changes: 16 additions & 4 deletions json_serialization/writer.nim
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,21 @@ proc writeJsonValueRef*[F,T](w: var JsonWriter[F], value: JsonValueRef[T]) =
of JsonValueKind.Null:
append "null"

template writeValue*(w: var JsonWriter, value: enum) =
# We extract this as a template because
# if we put it into `proc writeValue` below
# the Nim compiler generic cache mechanism
# will mess up with the compile time
# conditional selection
mixin writeValue
type Flavor = type(w).Flavor
when Flavor.flavorEnumRep() == EnumAsString:
w.writeValue $value
elif Flavor.flavorEnumRep() == EnumAsNumber:
w.stream.writeText(value.int)
elif Flavor.flavorEnumRep() == EnumAsStringifiedNumber:
w.writeValue $value.int

proc writeValue*(w: var JsonWriter, value: auto) {.gcsafe, raises: [IOError].} =
mixin writeValue

Expand Down Expand Up @@ -332,10 +347,7 @@ proc writeValue*(w: var JsonWriter, value: auto) {.gcsafe, raises: [IOError].} =

elif value is bool:
append if value: "true" else: "false"

elif value is enum:
w.writeValue $value


elif value is range:
when low(typeof(value)) < 0:
w.stream.writeText int64(value)
Expand Down
68 changes: 68 additions & 0 deletions tests/test_json_flavor.nim
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,71 @@ suite "Test JsonFlavor":
# field should not processed at all
let y = NullyFields.decode(jsonTextWithNullFields, ListOnly)
check y.list.string.len == 0

test "Enum value representation primitives":
when NullyFields.flavorEnumRep() == EnumAsString:
check true
elif NullyFields.flavorEnumRep() == EnumAsNumber:
check false
elif NullyFields.flavorEnumRep() == EnumAsStringifiedNumber:
check false

NullyFields.flavorEnumRep(EnumAsNumber)
when NullyFields.flavorEnumRep() == EnumAsString:
check false
elif NullyFields.flavorEnumRep() == EnumAsNumber:
check true
elif NullyFields.flavorEnumRep() == EnumAsStringifiedNumber:
check false

NullyFields.flavorEnumRep(EnumAsStringifiedNumber)
when NullyFields.flavorEnumRep() == EnumAsString:
check false
elif NullyFields.flavorEnumRep() == EnumAsNumber:
check false
elif NullyFields.flavorEnumRep() == EnumAsStringifiedNumber:
check true

test "Enum value representation of custom flavor":
type
ExoticFruits = enum
DragonFruit
SnakeFruit
StarFruit

NullyFields.flavorEnumRep(EnumAsNumber)
let u = NullyFields.encode(DragonFruit)
check u == "0"

NullyFields.flavorEnumRep(EnumAsString)
let v = NullyFields.encode(SnakeFruit)
check v == "\"SnakeFruit\""

NullyFields.flavorEnumRep(EnumAsStringifiedNumber)
let w = NullyFields.encode(StarFruit)
check w == "\"2\""

test "EnumAsString of custom flavor":
type
Fruit = enum
Banana = "BaNaNa"
Apple = "ApplE"
JackFruit = "VVV"

NullyFields.flavorEnumRep(EnumAsString)
let u = NullyFields.encode(Banana)
check u == "\"BaNaNa\""

let v = NullyFields.encode(Apple)
check v == "\"ApplE\""

let w = NullyFields.encode(JackFruit)
check w == "\"VVV\""

NullyFields.flavorEnumRep(EnumAsStringifiedNumber)
let x = NullyFields.encode(JackFruit)
check x == "\"2\""

NullyFields.flavorEnumRep(EnumAsNumber)
let z = NullyFields.encode(Banana)
check z == "0"
68 changes: 68 additions & 0 deletions tests/test_writer.nim
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,71 @@ suite "Test writer":
check uu.string == """{"a":123,"b":"nano","c":456}"""
let vv = YourJson.encode(y)
check vv.string == """{"a":null,"b":null,"c":999}"""

test "Enum value representation primitives":
when DefaultFlavor.flavorEnumRep() == EnumAsString:
check true
elif DefaultFlavor.flavorEnumRep() == EnumAsNumber:
check false
elif DefaultFlavor.flavorEnumRep() == EnumAsStringifiedNumber:
check false

DefaultFlavor.flavorEnumRep(EnumAsNumber)
when DefaultFlavor.flavorEnumRep() == EnumAsString:
check false
elif DefaultFlavor.flavorEnumRep() == EnumAsNumber:
check true
elif DefaultFlavor.flavorEnumRep() == EnumAsStringifiedNumber:
check false

DefaultFlavor.flavorEnumRep(EnumAsStringifiedNumber)
when DefaultFlavor.flavorEnumRep() == EnumAsString:
check false
elif DefaultFlavor.flavorEnumRep() == EnumAsNumber:
check false
elif DefaultFlavor.flavorEnumRep() == EnumAsStringifiedNumber:
check true

test "Enum value representation of DefaultFlavor":
type
ExoticFruits = enum
DragonFruit
SnakeFruit
StarFruit

DefaultFlavor.flavorEnumRep(EnumAsNumber)
let u = Json.encode(DragonFruit)
check u == "0"

DefaultFlavor.flavorEnumRep(EnumAsString)
let v = Json.encode(SnakeFruit)
check v == "\"SnakeFruit\""

DefaultFlavor.flavorEnumRep(EnumAsStringifiedNumber)
let w = Json.encode(StarFruit)
check w == "\"2\""

test "EnumAsString of DefaultFlavor/Json":
type
Fruit = enum
Banana = "BaNaNa"
Apple = "ApplE"
JackFruit = "VVV"

Json.flavorEnumRep(EnumAsString)
let u = Json.encode(Banana)
check u == "\"BaNaNa\""

let v = Json.encode(Apple)
check v == "\"ApplE\""

let w = Json.encode(JAckFruit)
check w == "\"VVV\""

Json.flavorEnumRep(EnumAsStringifiedNumber)
let x = Json.encode(JackFruit)
check x == "\"2\""

Json.flavorEnumRep(EnumAsNumber)
let z = Json.encode(Banana)
check z == "0"

0 comments on commit c539f41

Please sign in to comment.