arbre-app / read-gedcom

A modern Gedcom parser written in Typescript
https://docs.arbre.app/read-gedcom
MIT License
15 stars 4 forks source link

feature request: jsonify method for SelectionAny class #7

Closed lschierer closed 1 year ago

lschierer commented 1 year ago

I'm trying to use this with lit custom elements, and when passed between them, lit handles objects by running JSON.stringify(object) on the sending element and JSON.parse(incoming) on the receiving side.1 It would be great if you could implement the necessary method similar to the existing toString() but toJson instead so that it is reversible per the above.

Thanks!!

FlorianCassayre commented 1 year ago

Hi, thanks for your interest! I haven't checked in details yet the resources you linked, but I think that TreeNode is what you are looking for: these objects are the internal representation of the library, and in fact many people prefer to use this lower-level API instead. They support serialization out of the box.

The way you can obtain this instance is either by calling parseGedcom instead of readGedcom, OR given an instance returned by readGedcom you can access the rootNode property. You can convert a tree back to the high-level API with the constructor of SelectionGedcom (practically new SelectionGedcom(rootNode, [rootNode])). I don't think there is a more "user-friendly" of doing it, but this would probably be a good feature.

One important consideration when serializing: by default the index is included upon stringification, which can significantly increase the size of the resulting string. It is possible to exclude it from serialization, this is explained in this documentation section. Essentially, either you leave the configuration as is but your serialized string will be large, OR you set the parameter doHideIndex to true at parsing time, and call indexTree after deserialization to recompute it. The library can still work without an index, but it is highly recommend to have because it will cause performance issues.

Let me know if that helps, otherwise I would be happy to make the required changes to support this feature.

lschierer commented 1 year ago

So I had looked at this some time ago, and really struggled with it, but then I came across your work in https://github.com/arbre-app/browse-gedcom and using that as a starting point, I've made (what feels to me) like incredible progress compared to what I was capable of starting with a blank slate. I really needed the working example to mutate into my own because I'm not an incredibly strong programmer (thanks for your work by the way).

I'm currently working on producing an individual person display and, because as I said I started with your browse-gedcom as my example, I have parseGedcom getting fed into selectGedcom and the resulting SelectionGedcom stored in a variable that is exposed to my custom elements via context and reactive controller. All of this works fine, so you don't however really need to understand the internals of lit to follow.

The end result is that I'm calling getIndividualRecord method from the SelectionGedcom object, and getting back a SelectionIndividualRecord. I was attempting to pass this SelectionIndividualRecord from my base Individual class to an IndividualName component by attribute so that I do not have to re-query for the component nor declare a new context to share the selected individual down the tree. Learning that doing so requires the ability to JSONify the object lead to this report.

I'm sorry for writing a mini-book, I tend to the verbose. If there is a way to get from SelectionIndividualRecord to TreeNode and back, I'm happy to use that, but I suspect I will eventually need something similar for one or more of the SelectionFamily classes as well.

FlorianCassayre commented 1 year ago

Indeed, although browse-gedcom is now considered a legacy project, it's still a relevant resource (this application is aimed to be replaced by a more modern PWA -- it initially served as a PoC).

If there is a way to get from SelectionIndividualRecord to TreeNode and back, I'm happy to use that

Yes, here is how to do it:

const rootSelection: SelectionGedcom = ...;
const individualSelection: SelectionIndividual = ...;

// Assuming there is exactly one element in the selection:
const individualNode: TreeNode = individualSelection[0];

const serializedIndividual: string = JSON.stringify(individualNode);

// You can easily store this `serialized` string in e.g. a database, local storage, ...

// Now back to a node
const individualNode1: TreeNode = JSON.parse(serializedIndividual) as TreeNode;

Now if you need to obtain a selection out of this individual, you have no other choice that to serialize the root node: you cannot simply dissociate a child selection from its root. This is because selections allow you to query data from the entire "database", rather than just the children of a tree node. If you only need the tree object for a specific individual then you cannot use the selection API and instead should resort to the lower-level tree node API (which is fine, it just depends on your use case).

Here is the updated snippet that does what you requested:

// Same assumption
const individualNode: TreeNode = individualSelection[0];
const rootNode: TreeNodeRoot = individualSelection.rootNode;
const serialized: string = JSON.stringify({ rootNode, individualPointer: individualNode.pointer });

// And back
const { rootNode, individualPointer } =
  JSON.parse(serialized) as { rootNode: TreeNodeRoot, individualPointer: string | null };
const rootNode: SelectionGedcom = new SelectionGedcom(rootNode, [rootNode]);

// What you wanted:
const individualNode1: SelectionIndividual = rootNode.getIndividualRecord(individualPointer);

I haven't tested this code but the general idea should be correct. Of course you can abstract away some of the boilerplate if you're going to reuse this pattern elsewhere. Also don't save multiple copies of rootNode: this object is as large as the original Gedcom file, so you only need to store one instance of it.