Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize model serialization by using SharedObjects for reusable data #1090

Merged
merged 4 commits into from
Jan 22, 2025

Conversation

katehryhorenko
Copy link
Contributor

@katehryhorenko katehryhorenko commented Jan 16, 2025

🌌 BACKGROUND:
Suggestion functions generate numerous components for each space, many of which contain repeated information. This redundant data can be stored as shared objects to improve efficiency and reduce duplication.

📋 DESCRIPTION:
This PR:

  • Introduces SharedObjects serialization to the model, stored as a dictionary structure similar to Elements.
  • Adds support for skipping SharedObjects marked with the JsonIgnore attribute during serialization.
  • Optimizes elements referencing shared objects by:
    • Removing redundant properties from elements that are already present in the referenced shared object during serialization.

🧪 TESTING:

  • Tested with classroom and cafe functions to ensure proper functionality.
  • Verified that RepresentationInstances are not saved to JSON.
  • Confirmed that shared objects are correctly serialized and referenced, with no loss of critical information.

This change is Reviewable

@katehryhorenko katehryhorenko marked this pull request as ready for review January 17, 2025 21:32
Copy link
Member

@andrewheumann andrewheumann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 3 files at r1, all commit messages.
Reviewable status: 1 change requests, 0 of 1 approvals obtained (waiting on @katehryhorenko)


Elements/src/Model.cs line 40 at r1 (raw file):

        /// <summary>A collection of SharedObjects keyed by their identifiers.</summary>
        [JsonProperty("SharedObjects", Required = Required.Default)]
        [System.ComponentModel.DataAnnotations.Required]

just out of curiosity, what does this annotation do for us?

[System.ComponentModel.DataAnnotations.Required]

It seems like the json annotation says Required.Default which I think means "not required" — does that contradict what DataAnnotations.Required asserts?


Elements/src/Model.cs line 478 at r1 (raw file):

        }

        private (List<Element> elements, List<SharedObject> sharedObjects) RecursiveGatherSubElementsInternal(object obj, Dictionary<Type, List<PropertyInfo>> properties, out List<Element> sharedObjectSubElements)

Instead of mixing tuple return and out parameters, can we choose one or the other and use that only? I have a slight preference for the tuple return.

In general this method (which was already pretty confusing) seems to have gotten even harder to follow. Is there a way we can clean this up a bit (at least with comments) to make it clear what this method is doing and what different cases it addresses?


Elements/src/Model.cs line 508 at r1 (raw file):

            }

            var sharedObject = obj as SharedObject;

can we annotate what's happening here, and why it's a different case from

            if (sharedObject != null)
            {
                sharedObjects.Add(sharedObject);
            }

below? (IOW — why don't we need to add the shared object here)


Elements/src/Model.cs line 542 at r1 (raw file):

            }

            var elementsFromProperties = new List<Element>();

can we annotate what elementsFromProperties and sharedObjectsFromProperties are / represent?


Elements/src/Model.cs line 597 at r1 (raw file):

                        sharedObjectsFromProperties.AddRange(gatheredSubSharedObjects);
                    }
                    sharedObjectSubElements.AddRange(elementsFromPropertyToIgnore);

we repeat this chunk of code a lot. Can we consolidate, maybe to an internal method? something like

void processSubElements((List<Element> elements, List<SharedObject> sharedObjects, List<Element> sharedObjectSubElements) gatherResult, bool hasJsonIgnore, List<Element> outSubElements)
{
    var (subElements, subSharedObjects, elementsFromSharedObjectProperties) = gatherResult;
    elementsFromProperties.AddRange(subElements);
    // do not save shared objects marked with JsonIgnore
    if (!hasJsonIgnore)
    {
        sharedObjectsFromProperties.AddRange(subSharedObjects);
    }
    outSubElements.AddRange(elementsFromSharedObjectProperties);
}

(not sure what such a method should be called. I am not clear on what these different collections represent)


Elements/src/Serialization/JSON/JsonInheritanceConverter.cs line 288 at r1 (raw file):

        }

        private static bool WritingTopLevelSharedObject(string path)

This method is nearly identical to WritingTopLevelElement — can we rename it to PathIsTopLevel and pass in "Elements" / "SharedObjects"?

Copy link
Contributor Author

@katehryhorenko katehryhorenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 1 change requests, 0 of 1 approvals obtained (waiting on @andrewheumann)


Elements/src/Model.cs line 40 at r1 (raw file):

Previously, andrewheumann (Andrew Heumann) wrote…

just out of curiosity, what does this annotation do for us?

[System.ComponentModel.DataAnnotations.Required]

It seems like the json annotation says Required.Default which I think means "not required" — does that contradict what DataAnnotations.Required asserts?

Yep, we don't need it here


Elements/src/Model.cs line 478 at r1 (raw file):

Previously, andrewheumann (Andrew Heumann) wrote…

Instead of mixing tuple return and out parameters, can we choose one or the other and use that only? I have a slight preference for the tuple return.

In general this method (which was already pretty confusing) seems to have gotten even harder to follow. Is there a way we can clean this up a bit (at least with comments) to make it clear what this method is doing and what different cases it addresses?

I added GatherSubElementsResult class and moved a small part of logic there


Elements/src/Model.cs line 508 at r1 (raw file):

Previously, andrewheumann (Andrew Heumann) wrote…

can we annotate what's happening here, and why it's a different case from

            if (sharedObject != null)
            {
                sharedObjects.Add(sharedObject);
            }

below? (IOW — why don't we need to add the shared object here)

Done.


Elements/src/Model.cs line 542 at r1 (raw file):

Previously, andrewheumann (Andrew Heumann) wrote…

can we annotate what elementsFromProperties and sharedObjectsFromProperties are / represent?

Done.


Elements/src/Model.cs line 597 at r1 (raw file):

Previously, andrewheumann (Andrew Heumann) wrote…

we repeat this chunk of code a lot. Can we consolidate, maybe to an internal method? something like

void processSubElements((List<Element> elements, List<SharedObject> sharedObjects, List<Element> sharedObjectSubElements) gatherResult, bool hasJsonIgnore, List<Element> outSubElements)
{
    var (subElements, subSharedObjects, elementsFromSharedObjectProperties) = gatherResult;
    elementsFromProperties.AddRange(subElements);
    // do not save shared objects marked with JsonIgnore
    if (!hasJsonIgnore)
    {
        sharedObjectsFromProperties.AddRange(subSharedObjects);
    }
    outSubElements.AddRange(elementsFromSharedObjectProperties);
}

(not sure what such a method should be called. I am not clear on what these different collections represent)

Done.


Elements/src/Serialization/JSON/JsonInheritanceConverter.cs line 288 at r1 (raw file):

Previously, andrewheumann (Andrew Heumann) wrote…

This method is nearly identical to WritingTopLevelElement — can we rename it to PathIsTopLevel and pass in "Elements" / "SharedObjects"?

Done.

Copy link
Member

@andrewheumann andrewheumann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 2 files at r2, all commit messages.
Reviewable status: :shipit: complete! 1 of 1 approvals obtained (waiting on @katehryhorenko)


Elements/src/Model.cs line 37 at r2 (raw file):

            ///
            /// If shared object is marked as JsonIgnore (e.g. RepresentationInstance), it will not be
            /// serialized to JSON, but it's properties will be collected here so they can be used

nit: it's properties => its properties

Copy link
Member

@andrewheumann andrewheumann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind adding a few tests for SharedObject behavior?

Reviewable status: 1 change requests, 0 of 1 approvals obtained (waiting on @katehryhorenko)

Copy link
Contributor Author

@katehryhorenko katehryhorenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done :)

Reviewable status: 1 change requests, 0 of 1 approvals obtained

Copy link
Member

@andrewheumann andrewheumann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 2 files at r3, 1 of 1 files at r4, all commit messages.
Reviewable status: :shipit: complete! 1 of 1 approvals obtained

@katehryhorenko katehryhorenko merged commit 5fd519f into master Jan 22, 2025
2 checks passed
@katehryhorenko katehryhorenko deleted the sharedObjects branch January 22, 2025 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants