Add a pragma for customizing the field names in formats such as Json

This commit is contained in:
Zahary Karadjov 2020-05-20 13:48:14 +03:00
parent b61fcb51ad
commit 8a013591bd
No known key found for this signature in database
GPG Key ID: C8936F8A3073D609
1 changed files with 57 additions and 45 deletions

View File

@ -6,17 +6,19 @@ type
let let
# Identifiers affecting the public interface of the library: # Identifiers affecting the public interface of the library:
valueVar {.compileTime.} = ident "value" valueSym {.compileTime.} = ident "value"
readerVar {.compileTime.} = ident "reader" readerSym {.compileTime.} = ident "reader"
writerVar {.compileTime.} = ident "writer" writerSym {.compileTime.} = ident "writer"
holderVar {.compileTime.} = ident "holder" holderSym {.compileTime.} = ident "holder"
fieldNameVar {.compileTime.} = ident "fieldName"
FieldTypeSym {.compileTime.} = ident "FieldType"
template dontSerialize* {.pragma.} template dontSerialize* {.pragma.}
## Specifies that a certain field should be ignored for ## Specifies that a certain field should be ignored for
## the purposes of serialization ## the purposes of serialization
template serializedFieldName*(name: string) {.pragma.}
## Specifies an alternative name for the field that will
## be used in formats that include field names.
template enumInstanceSerializedFields*(obj: auto, template enumInstanceSerializedFields*(obj: auto,
fieldNameVar, fieldVar, fieldNameVar, fieldVar,
body: untyped) = body: untyped) =
@ -38,8 +40,12 @@ template enumInstanceSerializedFields*(obj: auto,
## ##
type ObjType = type(obj) type ObjType = type(obj)
for fieldNameVar, fieldVar in fieldPairs(obj): for fieldName, fieldVar in fieldPairs(obj):
when not hasCustomPragmaFixed(ObjType, fieldNameVar, dontSerialize): when not hasCustomPragmaFixed(ObjType, fieldName, dontSerialize):
when hasCustomPragmaFixed(ObjType, fieldName, serializedFieldName):
const fieldNameVar = getCustomPragmaFixed(ObjType, fieldName, serializedFieldName)
else:
const fieldNameVar = fieldName
body body
macro enumAllSerializedFieldsImpl(T: type, body: untyped): untyped = macro enumAllSerializedFieldsImpl(T: type, body: untyped): untyped =
@ -52,7 +58,12 @@ macro enumAllSerializedFieldsImpl(T: type, body: untyped): untyped =
## Inside the block body, the following symbols will be defined: ## Inside the block body, the following symbols will be defined:
## ##
## * `fieldName` ## * `fieldName`
## String literal for the field name ## String literal for the field name.
## The value can be affected by the `serializedFieldName` pragma.
##
## * `realFieldName`
## String literal for actual field name in the Nim type
## definition. Not affected by the `serializedFieldName` pragma.
## ##
## * `FieldType` ## * `FieldType`
## Type alias for the field type ## Type alias for the field type
@ -90,37 +101,42 @@ macro enumAllSerializedFieldsImpl(T: type, body: untyped): untyped =
let let
fieldType = field.typ fieldType = field.typ
fieldIdent = field.name fieldIdent = field.name
fieldName = newLit($fieldIdent) realFieldName = newLit($fieldIdent)
serializedFieldName = field.readPragma("serializedFieldName")
fieldName = if serializedFieldName == nil: realFieldName
else: serializedFieldName
discriminator = newLit(if field.caseField == nil: "" discriminator = newLit(if field.caseField == nil: ""
else: $field.caseField[0]) else: $field.caseField[0])
branches = field.caseBranch branches = field.caseBranch
fieldIndex = newLit(i) fieldIndex = newLit(i)
let fieldNameVarTemplate = let fieldNameDefs =
if isSymbol: if isSymbol:
quote: quote:
template `fieldNameVar`: auto {.used.} = `fieldName` const fieldName {.inject, used.} = `fieldName`
const realFieldName {.inject, used.} = `realFieldName`
else: else:
quote: quote:
template `fieldNameVar`: auto {.used.} = $`fieldIndex` const fieldName {.inject, used.} = $`fieldIndex`
const realFieldName {.inject, used.} = $`fieldIndex`
# we can't access .Fieldn, so our helper knows # we can't access .Fieldn, so our helper knows
# to parseInt this # to parseInt this
let field = let field =
if isSymbol: if isSymbol:
quote do: default(`T`).`fieldIdent` quote do: declval(`T`).`fieldIdent`
else: else:
quote do: default(`T`)[`fieldIndex`] quote do: declval(`T`)[`fieldIndex`]
result.add quote do: result.add quote do:
block: block:
`fieldNameVarTemplate` `fieldNameDefs`
type FieldType {.inject, used.} = type(`field`)
template fieldCaseDiscriminator: auto {.used.} = `discriminator` template fieldCaseDiscriminator: auto {.used.} = `discriminator`
template fieldCaseBranches: auto {.used.} = `branches` template fieldCaseBranches: auto {.used.} = `branches`
# type `fieldTypeVar` = `fieldType`
# TODO: This is a work-around for a classic Nim issue:
type `FieldTypeSym` {.used.} = type(`field`)
`body` `body`
i += 1 i += 1
@ -181,7 +197,7 @@ proc makeFieldReadersTable(RecordType, Reader: distinct type):
when RecordType is tuple: when RecordType is tuple:
const i = fieldName.parseInt const i = fieldName.parseInt
try: try:
type F = FieldTag[RecordType, fieldName, type(FieldType)] type F = FieldTag[RecordType, realFieldName, type(FieldType)]
when RecordType is tuple: when RecordType is tuple:
obj[i] = readFieldIMPL(F, reader) obj[i] = readFieldIMPL(F, reader)
else: else:
@ -190,14 +206,14 @@ proc makeFieldReadersTable(RecordType, Reader: distinct type):
# It seems to break the generics cache mechanism, which # It seems to break the generics cache mechanism, which
# leads to an incorrect return type being reported from # leads to an incorrect return type being reported from
# the `readFieldIMPL` function. # the `readFieldIMPL` function.
field(obj, fieldName) = FieldType readFieldIMPL(F, reader) field(obj, realFieldName) = FieldType readFieldIMPL(F, reader)
except SerializationError: except SerializationError:
raise raise
except CatchableError as err: except CatchableError as err:
reader.handleReadException( reader.handleReadException(
`RecordType`, `RecordType`,
fieldName, fieldName,
when RecordType is tuple: obj[i] else: field(obj, fieldName), when RecordType is tuple: obj[i] else: field(obj, realFieldName),
err) err)
result.add((fieldName, readField)) result.add((fieldName, readField))
@ -247,13 +263,14 @@ macro setSerializedFields*(T: typedesc, fields: varargs[untyped]): untyped =
template fieldPayload(fieldNameVar, fieldName, fieldVar, template fieldPayload(fieldNameVar, fieldName, fieldVar,
fieldAccessor, body) = fieldAccessor, body) =
block: block:
const fieldNameVar = fieldName const fieldNameVar {.inject, used.} = fieldName
template fieldVar: auto = fieldAccessor template fieldVar: auto {.used.} = fieldAccessor
body body
res.add getAst(fieldPayload(fieldNameVar, fieldName, fieldVar, res.add getAst(fieldPayload(fieldNameVar, fieldName,
fieldAccessor, body)) fieldVar, fieldAccessor,
body))
return res return res
macro enumAllSerializedFields*(typ: type T, body: untyped): untyped = macro enumAllSerializedFields*(typ: type T, body: untyped): untyped =
@ -263,27 +280,22 @@ macro setSerializedFields*(T: typedesc, fields: varargs[untyped]): untyped =
typ = getType(typ) typ = getType(typ)
for field in fields: for field in fields:
let let fieldName = newLit($field)
fieldName = newLit($field)
fieldNameVar = ident "fieldName"
FieldTypeSym = ident "FieldType"
# TODO replace with getAst once it's ready # TODO replace with getAst once it's ready
template fieldPayload(fieldNameVar, fieldName, template fieldPayload(fieldNameValue, typ, field, body) =
fieldTypeVar, typ, field,
body) =
block: block:
const fieldNameVar {.used.} = fieldName const fieldName {.inject, used.} = fieldNameValue
type fieldTypeVar {.used.} = type(default(typ).field) const realFieldName {.inject, used.} = fieldNameValue
type FieldType {.inject, used.} = type(declval(typ).field)
template fieldCaseDiscriminator: auto {.used.} = "" template fieldCaseDiscriminator: auto {.used.} = ""
template fieldCaseBranches: auto {.used.} = nil template fieldCaseBranches: auto {.used.} = nil
body body
res.add getAst(fieldPayload(fieldNameVar, fieldName, res.add getAst(fieldPayload(fieldName, typ, field, body))
FieldTypeSym, typ, field,
body))
return res return res
@ -322,16 +334,16 @@ proc genCustomSerializationForField(Format, field,
result.add quote do: result.add quote do:
type Reader = ReaderType(`Format`) type Reader = ReaderType(`Format`)
proc readFieldIMPL*(F: type FieldTag[`RecordType`, `fieldName`, auto], proc readFieldIMPL*(F: type FieldTag[`RecordType`, `fieldName`, auto],
`readerVar`: var Reader): `FieldType` = `readerSym`: var Reader): `FieldType` =
`readBody` `readBody`
if writeBody != nil: if writeBody != nil:
result.add quote do: result.add quote do:
type Writer = WriterType(`Format`) type Writer = WriterType(`Format`)
proc writeFieldIMPL*(`writerVar`: var Writer, proc writeFieldIMPL*(`writerSym`: var Writer,
F: type FieldTag[`RecordType`, `fieldName`, auto], F: type FieldTag[`RecordType`, `fieldName`, auto],
`valueVar`: auto, `valueSym`: auto,
`holderVar`: `RecordType`) = `holderSym`: `RecordType`) =
`writeBody` `writeBody`
proc genCustomSerializationForType(Format, typ: NimNode, proc genCustomSerializationForType(Format, typ: NimNode,
@ -341,13 +353,13 @@ proc genCustomSerializationForType(Format, typ: NimNode,
if readBody != nil: if readBody != nil:
result.add quote do: result.add quote do:
type Reader = ReaderType(`Format`) type Reader = ReaderType(`Format`)
proc readValue*(`readerVar`: var Reader, T: type `typ`): `typ` = proc readValue*(`readerSym`: var Reader, T: type `typ`): `typ` =
`readBody` `readBody`
if writeBody != nil: if writeBody != nil:
result.add quote do: result.add quote do:
type Writer = WriterType(`Format`) type Writer = WriterType(`Format`)
proc writeValue*(`writerVar`: var Writer, `valueVar`: `typ`) = proc writeValue*(`writerSym`: var Writer, `valueSym`: `typ`) =
`writeBody` `writeBody`
macro useCustomSerialization*(Format: typed, field: untyped, body: untyped): untyped = macro useCustomSerialization*(Format: typed, field: untyped, body: untyped): untyped =