planetis-m / eminim

JSON serialization framework for Nim, works from a Stream directly to any type and back. Depends only on stdlib.
Other
37 stars 6 forks source link

Support case objects #2

Closed planetis-m closed 3 years ago

planetis-m commented 5 years ago

Write code like:

var pairs: seq[(string, string)]
var kind: ObjKind
result = Obj(kind: kind)
for key, value in pairs:
  case kind
  of variantA:
  of variantB:
     case key
     of "fieldA":
        result.field = value
planetis-m commented 5 years ago

It wont work...

planetis-m commented 5 years ago

This will:

while readfields:
   case key
   of "kind":
      tmp_kind = readAny(value)
   of "fieldA":
      tmp_fieldA = readAny(value)
   of "fieldB":
      tmp_fieldB = readAny(value)
result = Obj(kind: tmp_kind)
case kind
of variantA:
   result.fieldA = tmp_fieldA
of variantB:
   result.fieldB = tmp_fieldB
planetis-m commented 3 years ago

Example

type
  Fruit = enum
    Apple
    Banana

  Foo = object
    name: string
    case kind: Fruit
    of Banana: banana: int
    of Apple: apple: string

let
  original = Foo(name: "hello", kind: Apple, apple: "world")
let s = newStringStream("""{"name":"hello","apple":"world","kind":"Apple"}""")
echo s.to(Foo)

Warning pseudocode:

proc packImpl(p: var JsonParser): Foo =
  var prev = -1
  while readfields:
    case key
    of "name":
      tmp_name = readAny(value)
    of "kind":
      tmp_kind = readAny(value)
    of "banana", "apple":
      if prev != -1: prev = p.stream.getPosition() # impossible
    else:
      error("object field")
  result = Obj(name: tmp_name, kind: tmp_kind)
  if prev != -1: p.stream.setPosition(prev)
  while readfields:
    case kind
    of Banana:
      case key
      of "banana":
        result.banana = readAny(value)
      of "name", "kind": discard
      else:
        error("object field")
    of Apple:
      case key
      of "apple":
        result.apple = readAny(value)
      of "name", "kind": discard
      else:
        error("object field")

However LexBase doesn't allow backtracking, so I might do this:

proc packImpl(p: var JsonParser): Foo =
  while readfields:
    case key
    of "name":
      tmp_name = readAny(value)
    of "kind":
      tmp_kind = readAny(value)
    else:
      error("object field")
  result = Obj(name: tmp_name, kind: tmp_kind)
  while readfields:
    case kind
    of Banana:
      case key
      of "banana":
        result.banana = readAny(value)
      of "name":
        result.name = readAny(value)
      else:
        error("object field")
    of Apple:
      case key
      of "apple":
        result.apple = readAny(value)
      of "name"
        result.name = readAny(value)
      else:
        error("object field")

Ofcourse this still holds and needs to be considered:

proc packImpl(p: var JsonParser): Foo =
  while readfields:
    case key
    of "name":
      tmp_name = readAny(value)
    of "kind":
      tmp_kind = readAny(value)
    of "banana":
      isBanana = true
      tmp_banana = readAny(value)
    of "apple":
      isApple = true
      tmp_apple = readAny(value)
    else:
      error("object field")
  result = Obj(kind: tmp_kind)
  if not(isBanana xor isApple):
    raise newException(FieldDefect, "field is not accessible using discriminant " & $tmp_kind)
  result.name = tmp_name
  case kind
  of Banana:
    result.banana = tmp_banana
  of Apple:
    result.apple = tmp_apple
planetis-m commented 3 years ago

Done. For now it expects first the discriminator field. Its sufficient for me but PRs welcome.