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

Add JS Map support for encoding & decoding #5

Merged
merged 4 commits into from
Mar 12, 2024
Merged

Conversation

jasonpaulos
Copy link

@jasonpaulos jasonpaulos commented Mar 1, 2024

Adds support for encoding and decoding Map objects, as an alternative to plain javascript objects.

The primary advantage is that this adds much better non-string type support for map keys.

src/Decoder.ts Outdated
return typeof key === "string" || typeof key === "number" || typeof key === "bigint" || key instanceof Uint8Array;
}
// Plain objects support a more limited set of key types
return typeof key === "string";

Choose a reason for hiding this comment

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

This is a breaking change, should we keep the existing logic looking for number too?

Copy link
Author

Choose a reason for hiding this comment

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

It's pretty strange to me that the upstream library was getting away with just coercing number keys to strings on objects, so I wanted to do away with that behavior now that there's a better alternative.

Although, since it is a breaking change, I think I'd be willing to add another decoding option that could control that behavior if it's really what you wanted, since this has been an established practice in the upstream library for a while.

src/Decoder.ts Outdated
@@ -650,7 +675,7 @@ export class Decoder<ContextType = undefined> {
}

private decodeString(byteLength: number, headerOffset: number): string | Uint8Array {
if (!this.useRawBinaryStrings || this.stateIsMapKey()) {
if (!this.useRawBinaryStrings || (!this.useMap && this.stateIsMapKey())) {

Choose a reason for hiding this comment

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

If I read this right, does it mean if you are using maps and you set useRawBinaryStrings then it will use a uint8array for the map keys? I wonder if we should allow that to be switchable?

Copy link
Author

Choose a reason for hiding this comment

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

That's correct, and your suggestion makes sense

Copy link
Author

Choose a reason for hiding this comment

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

Added two options for this in 7342b02

@robdmoore
Copy link

robdmoore commented Mar 5, 2024 via email

@jasonpaulos
Copy link
Author

Here's why I don't like decoding number keys on plain objects:

  • No existing test coverage for it
  • Potential for collision with string properties
    • Admittedly this is not a real concern for us, since in Algorand I don't believe we have any map objects that use both numbers and strings as keys. However, I find this behavior by default to be a big oversight for an encoding library
  • Breaks for bigints
    • Any numbers decoded as bigints can't be keys. For an encoding scheme like this that supports 64-bit numbers, that means failures can occur just based on the value of those numbers. Depending on which int decoding mode you use, this could mean no numeric keys are supported, or only small numeric keys are supported.
  • Inconsistent round trip results
    • Since numeric object keys get coerced into strings, there's no way of re-encoding your object identically. This is a huge pitfall.

Since it does happen to work with plain objects, it was a good way to offer partial support for number map keys. But now that this PR introduces full support, I don't think the advantages for enabling it by default outweigh the cons I listed above.

For backwards compatibility, I did add the supportObjectNumberKeys option in 7342b02. I think this might be a good compromise, since you can easily enable the old behavior

@jasonpaulos jasonpaulos marked this pull request as ready for review March 5, 2024 22:01
@jasonpaulos jasonpaulos requested a review from jannotti March 6, 2024 16:01
Copy link

@jannotti jannotti left a comment

Choose a reason for hiding this comment

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

I haven't spent much time in the tests yet.

src/Decoder.ts Show resolved Hide resolved
src/Encoder.ts Show resolved Hide resolved
src/Decoder.ts Show resolved Hide resolved
src/Decoder.ts Outdated Show resolved Hide resolved
@jasonpaulos jasonpaulos requested a review from jannotti March 8, 2024 15:19
Copy link

@jannotti jannotti left a comment

Choose a reason for hiding this comment

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

I didn't see an floating point tests, those could be useful in the abstract, but of course Algorand doesn't use them.

@jasonpaulos jasonpaulos merged commit b92d75b into main Mar 12, 2024
7 checks passed
@jasonpaulos jasonpaulos deleted the js-map-support branch March 12, 2024 16:35
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.

3 participants