flyx / NimYAML

YAML implementation for Nim
https://nimyaml.org
Other
191 stars 36 forks source link

dump objects with private fields gives Error: undeclared field #72

Closed travisstaloch closed 5 years ago

travisstaloch commented 5 years ago

Is there any way to dump objects with non-exported (private) fields?

Here is a minimal example and error message:

# point.nim - only triggers error if in separate file from dump() call.  
type
  Point* = object
    x:int
    y*:int
# test_point.nim
import point, yaml/serialization
let p = Point(y:2)
let s = dump(p)

.nimble\pkgs\yaml-#devel\yaml\serialization.nim(1017, 18) Error: undeclared field: 'x' for type point.Point

I am on nim 0.20 using k0zmo's devel branch

I would like to be able to dump the object, skipping the private fields.

flyx commented 5 years ago

If your field is private, you must put the YAML dumping/loading code in the package that defines the type. That's the whole point of the field being private.

More generally speaking, serialization is and has always been the enemy of information hiding. A serialization library needs access to private data, when all other purposes you use a package for should really be forbidden to do it. NimYAML's solution is to generate serialization code via generics, template instantiation and macros in the package where you call dump, and that needs to be the package that has access to the private information.

At least, that's the concept. Actually, I'm not sure if it works, so just try and define a helper function in point.nim that calls dump, and then call that in test_point.nim and report your findings :).

travisstaloch commented 5 years ago

I see what you mean. I discovered that nim's json package has the same behavior, not allowing dumping/loading objects with private fields. I was hoping to be able to (de)serialize some 3rd party objects without having to write much code. I guess its not too much hassle to include the package and define these dumping/loading procs. I'll likely make a macro for this.

This works. Thanks for your suggestion and thoughts.

# point.nim
import yaml/serialization, streams

type
  Point* = object
    x:int
    y*:int

proc dumpPoint*(p: Point): string =
  dump(p)

proc loadPoint*(s: string): Point =
  let ss = newStringStream(s)
  load(ss, result)
#test_point.nim
    let
      p = Point(y:2)
      s = dumpPoint(p)
      y = "%YAML 1.2\nx: 0\ny: 2\n"
      p = loadPoint(y)

You have to provide values for each field to load. If either x or y is missing:

Unhandled exception: While constructing Point: Missing field: "y" [YamlConstructionError] [FAILED] load point

flyx commented 5 years ago

If you have a third-party library, you potentially could use include instead of import which makes the declarations part of your package (e.g. original file is foo, your file is enhancedFoo and has include foo; all your other files must depend on enhancedFoo instead of foo). However, that won't work as soon as other third-party code imports the original package.