Add a pragma for customizing the field names in formats such as Json
This commit is contained in:
parent
b61fcb51ad
commit
8a013591bd
|
@ -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 =
|
||||||
|
|
Loading…
Reference in New Issue