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

load() and DOM APIs #557

Merged
merged 10 commits into from
Feb 20, 2020
Merged

load() and DOM APIs #557

merged 10 commits into from
Feb 20, 2020

Conversation

zslayton
Copy link
Contributor

Issue #, if available: #531

Description of changes:

Currently, ion-js only provides a streaming Reader API to access values in an Ion stream. This API is efficient, but has a rather steep learning curve and can often require verbose code. This PR creates a DOM-style API for loading values from an Ion stream into memory, allowing users to interact with them as they would any other JS value/Array/Object.

load() and loadAll()

Two new top-level functions have been introduced:

// Loads the first value from the provided data source (if present) and returns it
load(ionData: ReaderBuffer | Reader): Value | null

// Loads all of the values found in the provided data source and returns them
loadAll(ionData: ReaderBuffer | Reader): Value[]

Javascript (any) interface

Each Ion data type is represented by a new class that extends an existing Javascript type:

  • dom.List extends Array
  • dom.Integer extends Number
  • dom.Timestamp extends Date
  • ....

This means that in most cases, you can treat values returned by load() the same way you would treat a standard Javascript equivalent.

// Ion integer
let i: any = ion.load('7');
assert.equal(7, i);
assert.equal(8, i + 1);
assert.equal(49, i ** 2);
assert.equal(1, i / 7);
// Ion string
let s: any = ion.load('"Saturn"');
assert.equal('Saturn', s);
assert.equal('Saturn' + ', Jupiter', s + ', Jupiter');
assert.equal('Saturn'.substr(4), s.substr(4));
assert.isNotNull(s.match(/atu/));
// Ion list
let l: any = ion.load('[0, 2, 4, 6, 8, 10]');
assert.equal(4, l[2]);
assert.equal(6, l.length);
// iteration example
for (let value of l) {
    assert.isTrue(value % 2 == 0);
}
// Direct access to Array methods
assert.equal(30, l.reduce((sum, value) => sum + value, 0));
// Ion struct
let person: any = ion.load(
  'person::{' +
      'name: {' +
          'first: "John", ' +
          'middle: "Jacob", ' +
          'last: "Jingleheimer-Schmidt",' +
     '},' +
     'age: 41' +
  '}'
);
assert.equal(41, person.age);
assert.equal(41, person['age']);
assert.equal('Jacob', person.name.middle);
for (let [key, value] of person) { // Iteration
    console.log(name + ': ' + value);
}

Typescript (Value) interface

The Javascript API above is concise and convenient, but does not offer any static type safety. Each of the new classes in this PR also implements a new interface called dom.Value, which provides methods for indexing into Ion values as well as converting them to JS representations.

// Ion integer
// `i` is an implementation of the Value interface. To use it, we'll need to
// be explicit about our coercions to JS types in order to satisfy 
// the Typescript compiler.
let i: Value = ion.load('7');

// The unary '+' operator will coerce our dom.Integer into a JS number
assert.equal(7, +i);
// We can explicitly convert this value to a JS number
assert.equal(7, i.numberValue()); 

// dom.Integer is not a text value, so there is no string value associated with it.
assert.isNull(i.stringValue());
// Ion list
let l: Value = ion.load('[0, 2, 4, 6, 8, 10]');

// We can use the `get()` method to index into the Value
assert.equal(4, l.get(2));

// iteration example using the `values()` method
for (let value of l.values()!) {
    assert.isTrue(value % 2 == 0);
}
// Ion struct
let person: Value = ion.load(
  'person::{' +
      'name: {' +
          'first: "John", ' +
          'middle: "Jacob", ' +
          'last: "Jingleheimer-Schmidt",' +
     '},' +
     'age: 41' +
  '}'
)!;
assert.equal(41, person.get('age'));
assert.equal('Jacob', person.get('name', 'middle'));

// Iteration example using the `fields()` method
for (let [key, value] of person.fields()) {
    console.log(name + ': ' + value);
}

Recommended reading order

I suggest reading the following files first:

  1. src/dom/Value.ts: All of the new classes introduced in this PR implement this interface.
  2. src/dom/DomValue.ts: Serves as a common default implementation for the Value interface.
  3. src/dom/index.ts: Defines the load() and loadAll() methods.
  4. test/dom/dom.ts: Demonstrates how to work with each data type after load()ing some Ion data.

The rest of the files contain class definitions for each Ion type and are pretty straightforward.

Notes

  1. I tested out a few different naming conventions before settling on using the plain Ion type names themselves grouped under the dom module. There are some name collisions that are easily remedied via package qualifiers or custom import aliases:
  • ion.dom.String vs global.String
  • ion.dom.Boolean vs global.Boolean
  • ion.dom.Decimal vs ion.Decimal
  • ion.dom.Timestamp vs ion.Timestamp
  1. The provided implementation of toString() does not attempt to produce valid Ion. Overriding toString() in classes that extend String (i.e. dom.String, dom.Symbol) causes some String functionality (e.g. the match method) to break.

Follow-on work

  1. We still need to provide an ion.dump(...) method to perform the inverse of ion.load(). I held off on this to confirm that the API proposed in this PR is acceptable.
  2. There are some TODOs in this code that will be turned into ion-js issues after the PR gets merged. Another PR will add the issue links to the comments.
  3. There are some constructors that could/should take a wider variety of data types. For example, it should be possible to create a dom.Decimal from a number. However, this is not required for use of the load() API.
  4. Once an ion.dump() method is available, we should design a formal integration with ion-tests.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@zslayton
Copy link
Contributor Author

The tests are running fine, but it looks like there's an issue with typedoc generation. I'm looking into it.

Copy link
Contributor

@almann almann left a comment

Choose a reason for hiding this comment

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

Overall, this looks pretty good, some questions/comments below.

src/dom/Value.ts Outdated
Comment on lines 71 to 85
/**
* Returns null if the Value is not a Struct, List, or SExpression.
* Otherwise, will attempt to index into the Value with each pathElement in turn
* until:
* - A value is found that cannot use the current PathElement as an index.
* (e.g. The current Value is a List but the current PathElement is a number.)
* - An undefined value is encountered.
* (e.g. The current Value is a Struct and the current PathElement is a string,
* but the current PathElement is not a fieldName that exists in the Struct.)
* - The pathElements are exhausted and the requested Value is discovered.
*
* @param pathElements One or more values to be used to index into the Value.
* @returns null if no value is found at the specified path. Otherwise, returns the discovered Value.
*/
get(...pathElements: PathElement[]): Value | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we use undefined here? I ask because of the potential asymmetry with respect to someone using null as a value on the serialization side.

Choose a reason for hiding this comment

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

-1 for undefined. If pathElements resolves to a null value, I'm expecting an instance of Value to be returned for which isNull() returns true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ion null values loaded from a stream are represented in the DOM as instances of dom.Null (which implements dom.Value). If get(...) returns a Javascript null, it always means that no value was found at the specified path.

In the upcoming serialization API, if you wrote:

let value: Value = ion.dump(null);

I would expect value to be an instance of dom.Null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If pathElements resolves to a null value, I'm expecting an instance of Value to be returned for which isNull() returns true.

Can you elaborate on this? If there isn't an Ion value at the specified path, why would an instance of Value be returned?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To clarify the current behavior:

If pathElements resolves to a null value

  • If pathElements resolves to an Ion null, get will currently return a Value for which isNull() is true and getType() returns the correct Ion type.
  • If pathElements resolves to a JS null, it means that no value was found at the specified path.

Comment on lines 46 to 53
this._ionType = ionType;
this._annotations = [];
// Setting the 'enumerable' attribute of these properties to `false` prevents them
// from appearing in the iterators returned by Object.keys(), Object.entries(), etc.
// This guarantees that users iterating over the fields of a struct or values in a list
// will see only Values from the source data or that they have created themselves.
Object.defineProperty(this, "_ionType", {enumerable: false});
Object.defineProperty(this, "_annotations", {enumerable: false});
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor: We may want to have _annotations be spelled _ionAnnotations just to be consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, good call.

Comment on lines 60 to 63
// Class expressions (like this mixin) cannot have private or protected methods.
_setAnnotations(annotations: string[]) {
this._annotations = annotations;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the implication that one cannot set annotations or is that handled elsewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The intent was for users not to mutate the annotations that were set during construction (at least for now). Ordinarily you could signal this by flagging the method as protected or private, but that mechanism isn't permitted in class expressions. I've used the _ prefix (as is done elsewhere in ion-js) to signal that this is meant to be an internal method, but I added the comment to clarify that I couldn't provide stronger guarantees.

src/dom/Value.ts Outdated
Comment on lines 27 to 51
/**
* Returns null if the Value is not a Boolean.
* Otherwise, returns the boolean value of the Boolean.
*/
booleanValue(): boolean | null;

/**
* Returns null if the Value is not an Integer, Float, or Decimal.
* Otherwise, returns the JS number representation of the Value.
* In some cases, this method will perform conversions that inherently involve loss of
* precision. See bigIntValue() and decimalValue() for lossless alternatives.
*/
numberValue(): number | null;

/**
* Returns null if the Value is not an Integer.
* Otherwise, returns a lossless BigInt representation of the Value.
*/
bigIntValue(): JSBI | null;

/**
* Returns null if the Value is not a Decimal.
* Otherwise, returns a lossless Decimal representation of the Value.
*/
decimalValue(): Decimal | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

A common theme in this API is that for the cases where the type does not match, null is returned. What if the type aligns and the value is null? Should the mis-typed cases throw (similar to Java) or return something like undefined?

Choose a reason for hiding this comment

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

-1 for undefined (there are times when it is tempting, but still poor imo), but throwing sounds appropriate as it represents a coding error which would ideally be identified by the compiler (but can't be in these cases). Throwing would also align with ion-js's IonReader contract.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What if the type aligns and the value is null?

I don't believe there is such a case. Can you give me an example?

Note that:

let value: Value = ion.load('"foo"');
assert.equal(value.getType(), IonTypes.STRING);
assert.equal("foo", value.stringValue());
assert.isFalse(value.isNull());
assert.isTrue(value instanceof ion.dom.String);
assert.isFalse(value instanceof ion.dom.Null);

let value: Value = ion.load('null.string');
assert.equal(value.getType(), IonTypes.STRING);
assert.isNull(value.stringValue());
assert.isTrue(value.isNull());
assert.isFalse(value instanceof ion.dom.String);
assert.isTrue(value instanceof ion.dom.Null);

src/dom/Value.ts Outdated
Comment on lines 93 to 97
/**
* Returns null if the Value is not a Struct.
* Otherwise, returns an iterator over the field name/value pairs in the Struct.
*/
fields(): IterableIterator<[string, Value]> | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why return null over throwing?

Choose a reason for hiding this comment

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

+1 for throwing (as well as for fieldNames() and values())

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went back and forth on this. ion-java throws.

I opted to return null because it allows users to cheaply test for unexpectedly missing values.

let greeting = ion.load('hello')!.stringValue();
if (greeting !== null) {
    console.log(greeting + ", friend.");
}

If we throw, the only way to avoid the expense of throwing and catching an Error would be rather tedious type inspection.

let greeting: Value = ion.load('hello')!;
if (value.getType() === IonTypes.STRING && !value.isNull()) { // OR  (value instanceof dom.String)
  console.log(greeting.stringValue());
}

src/dom/index.ts Outdated
Comment on lines 64 to 72
function createReader(ionData: ReaderBuffer | Reader): Reader {
// If the provided parameter is already a reader, no new work is required.
// However, we cannot simply test `ionData instanceof Reader` because `Reader`
// is an interface.
if (ionData instanceof TextReader || ionData instanceof BinaryReader) {
return ionData as Reader;
}
return makeReader(ionData as ReaderBuffer);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it cleaner to test against the concrete types of ReaderBuffer to avoid the potentially more open concrete type space of Reader? (e.g. mitigating when we add a DOM Reader or something in the future).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting observation. I'll switch it over.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I remember the original challenge I encountered here. ReaderBuffer is comprised of:

  • string
  • ReaderOctetBuffer
    • ArrayBufferLike
      • ArrayBuffer
        • ArrayBufferView
        • ... (different view sizes)
      • SharedArrayBuffer
      • ...
    • ArrayLike<number>

Many of these are interfaces or type aliases and do not exist at runtime. There's no comprehensive list of concrete types we can test for with (e.g.) typeof or instanceof.

I'm going to leave this as-is for now. I suggest providing a public AbstractReader class as a common base class for Reader implementations, even if it offers no concrete functionality. Then we can simply test instanceof AbstractReader to make this distinction.

src/dom/Null.ts Outdated
Comment on lines 11 to 14
* text encoding | Ion data type
* ======================================
* null | IonType.NULL (implicit)
* null.null | IonType.NULL (explicit)
Copy link
Contributor

Choose a reason for hiding this comment

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

What is implicit vs. explicit mean here? null.null and null are merely syntactic ways to spell the same thing, this doc seems to imply something more, but I can't think of what could be meant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

null.null and null are merely syntactic ways to spell the same thing

This is all I meant to say. One encoding spells out the Ion type, one doesn't. I'll remove the words "implicit" and "explicit" from the comment, since they appear to do more harm than good.

Comment on lines 16 to 27
constructor(fields: IterableIterator<[string, Value]>, annotations: string[] = []) {
super();
for(let [fieldName, value] of fields) {
Object.defineProperty(this, ''+fieldName, {
configurable: true,
enumerable: true,
value: value,
writable: true
})
}
this._setAnnotations(annotations);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is burying the lede if I am reading this correctly. You do surface field names as properties, yes? If so, this should be documented explicitly in the doc comment.

What happens when they incidentally shadow methods or the internal fields? Does this need to gate against that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You do surface field names as properties, yes?

Yes.

If so, this should be documented explicitly in the doc comment.

Agreed, I'll add this.

What happens when they incidentally shadow methods or the internal fields? Does this need to gate against that?

My plan (not implemented in this already quite large PR) is for all property accesses on structs to resolve to method names and internal fields first. If you have data that would shadow one of those fields, it can be accessed by .get(fieldName) instead. The iterators returned by fields() and fieldNames() would behave properly, returning the custom property values instead of the methods/fields.

let s = ion.load('{stringValue: "Hello", foo: "bar"}')!; // The `stringValue` field shadows Value#stringValue()
let greeting = s.stringValue; // This resolves to the method handle and is probably not what the user wanted
let greeting = s['stringValue']; // This resolves to the method handle and is probably not what the user wanted
let greeting = s.stringValue(); // `greeting` is now "Hello"
let greeting = s.get('stringValue'); // `greeting is now "Hello"

This means that there are ~16 Struct field names that can have unexpected behavior, which seemed a better compromise than simply forbidding them.

Comment on lines 24 to 26
stringValue(): string {
return this.valueOf() as string;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this class override toString like dom.String does?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, thanks. It's already storing symbolText: string, so we might as well avoid the cost of converting from String to string via valueOf(). I'll add that.

Copy link

@pbcornell pbcornell left a comment

Choose a reason for hiding this comment

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

Good work, Zack. :) Two high-level thoughts:

  • please check the first parameter of the dom-class constructors for consistency. should others specify protected readonly? looks like we're not using a "_" prefix consistently? the following four stood out, but worth checking all of them:
    • Clob: protected readdonly bytes
    • Float: protected readonly value
    • String: protected readonly _text
    • Symbol: protected readonly symbolText
  • it's entirely possible i don't understand the beauty of [...x] ;), but i expect x to be equivalent; am i missing out on the magic?

src/dom/Value.ts Outdated Show resolved Hide resolved
src/dom/Value.ts Outdated
Comment on lines 27 to 51
/**
* Returns null if the Value is not a Boolean.
* Otherwise, returns the boolean value of the Boolean.
*/
booleanValue(): boolean | null;

/**
* Returns null if the Value is not an Integer, Float, or Decimal.
* Otherwise, returns the JS number representation of the Value.
* In some cases, this method will perform conversions that inherently involve loss of
* precision. See bigIntValue() and decimalValue() for lossless alternatives.
*/
numberValue(): number | null;

/**
* Returns null if the Value is not an Integer.
* Otherwise, returns a lossless BigInt representation of the Value.
*/
bigIntValue(): JSBI | null;

/**
* Returns null if the Value is not a Decimal.
* Otherwise, returns a lossless Decimal representation of the Value.
*/
decimalValue(): Decimal | null;

Choose a reason for hiding this comment

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

-1 for undefined (there are times when it is tempting, but still poor imo), but throwing sounds appropriate as it represents a coding error which would ideally be identified by the compiler (but can't be in these cases). Throwing would also align with ion-js's IonReader contract.

src/dom/Value.ts Show resolved Hide resolved
src/dom/Value.ts Show resolved Hide resolved
src/dom/Value.ts Outdated Show resolved Hide resolved
test/dom/dom.ts Show resolved Hide resolved
test/dom/dom.ts Show resolved Hide resolved
test/dom/dom.ts Outdated Show resolved Hide resolved
test/dom/dom.ts Outdated Show resolved Hide resolved
test/dom/dom.ts Outdated Show resolved Hide resolved
* Value's [type]Value() functions will throw an Error if the
  requested operation is not supported by the Value's ionType.
* Value's fields(), fieldNames(), and elements() methods return
  arrays instead of iterators.
* ion.load() will only call next on the provided Reader if it is
  not already positioned over a value.
* Cleanup for spacing, typedoc, etc.
@zslayton
Copy link
Contributor Author

zslayton commented Feb 14, 2020

Updates

The most recent commit includes the following changes:

  • Renamed Value's _annotations field to _ionAnnotations.
  • Value's [type]Value() functions will throw an Error if the requested operation is not supported by the Value's ionType.
  • Null now contains type checking logic to see if a conversion should succeed but return null (e.g. ion.load('null.string').stringValue() === null) or if it should fail due to a type mismatch (e.g. ion.load('null.int').stringValue() produces an Error)
  • Added a timestampValue(): Timestamp | null function alongside dateValue() for lessless access to ion Timestamp values.
  • Renamed the values() method to elements() so it no longer shadows Array#values, which returns an iterator.
  • Value's fields(), fieldNames(), and elements() methods return arrays instead of iterators. This allows users to call map, filter, join, etc on the results of those methods natively instead of collecting their output into an intermediate array via [...value.elements()] first. Somewhat surprisingly, JS values often return an array for methods you might expect to return an iterator. For examples, see:
  • ion.load() will only call next on the provided Reader if it is not already positioned over a value.
  • Several classes no longer store their constructor parameters as a member field. In some cases it may have offered a micro-optimization when converting to a JS primitive, but it required an amount of complexity that we can hold off on until we need it.
  • Fixed a number of spacing issues/typos that Peter identified.

Blocking issue

I've identified a bug in typedoc's mixin support that is currently causing the build to fail. We should discuss short term workarounds to avoid unnecessary delays.

@zslayton zslayton requested review from pbcornell and almann and removed request for wesboyt February 14, 2020 20:39
Due to an issue[1] in typedoc's mixin support,
the documentation generation phase of the
release process fails.

This commit points the typedoc dependency to a
custom fork[2] that contains a fix. We can and
should back this change out later when
mainline is fixed.

[1] TypeStrong/typedoc#1207
[2] https://github.com/zslayton/typedoc/
@zslayton
Copy link
Contributor Author

I've unblocked the build by creating a temporary typedoc fork with a patch. package.json has been tweaked to point to the fork until a fix is available.

@zslayton
Copy link
Contributor Author

zslayton commented Feb 16, 2020

The typedoc patch was merged and released in v0.16.10 over the weekend. (Thanks, @Gerrit0!)

I've updated the typedoc dependency to point to the new version.

src/dom/index.ts Outdated Show resolved Hide resolved
src/dom/index.ts Outdated Show resolved Hide resolved
src/dom/index.ts Outdated Show resolved Hide resolved
src/dom/Null.ts Outdated Show resolved Hide resolved
src/dom/Null.ts Outdated
if (Null._operationIsSupported(this.getType(), operation)) {
return null;
}
throw new Error(`'${operation}' is not supported by Ion type ${this.getType().name}`);

Choose a reason for hiding this comment

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

drop single quotes, add ()? as in: ${operation}() is not supported...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure.

src/dom/Null.ts Outdated
// Otherwise, throw an Error indicating that the requested operation is not supported.
private _unsupportedOperationOrNullDerefence(operation: string): never {
if (Null._operationIsSupported(this.getType(), operation)) {
throw new Error(`${operation} called on a null ${this.getType().name}.`);

Choose a reason for hiding this comment

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

add (), as in: ${operation}() called...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

src/dom/Null.ts Outdated
if (Null._operationIsSupported(this.getType(), operation)) {
throw new Error(`${operation} called on a null ${this.getType().name}.`);
}
throw new Error(`'${operation}' is not supported by Ion type ${this.getType().name}`);

Choose a reason for hiding this comment

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

drop single quotes around ${operation} ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link

@pbcornell pbcornell left a comment

Choose a reason for hiding this comment

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

lgtm :)

Copy link
Contributor

@almann almann left a comment

Choose a reason for hiding this comment

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

Overall looks great--minor comments below.

super(value);
this._setAnnotations(annotations);
}

public numberValue(): number {
return this.valueOf() as number;
return +this.valueOf();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

A comment of intent here would be helpful as to what unary plus is trying to accomplish here (presumably forces some kind of coercion?).

['fields', new Set([IonTypes.STRUCT])],
['fieldNames', new Set([IonTypes.STRUCT])],
['elements', new Set([IonTypes.LIST, IonTypes.SEXP, IonTypes.STRUCT])]
]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Magic strings always makes me a bit nervous, would it be prudent to just model these symbols as an enum? Also, do we really need this table to be a map? Wouldn't an enumeration of integers serve this purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm going to see if I can add the supported types as a property directly on the methods' Function objects. I'll do so in an upcoming PR.

Comment on lines 8 to 13
* Struct fields can be accessed as properties on this object. For example:
*
* let s: any = ion.load('{foo: 1, bar: 2, baz: qux::3}');
* assert.equal(6, s.foo + s['bar'] + s.baz);
*
* [1] http://amzn.github.io/ion-docs/docs/spec.html#struct
Copy link
Contributor

Choose a reason for hiding this comment

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

If someone defines a field name that collides with a method name, which is masked? We should be clear about that in the docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have a PR brewing that will introduce the new methods-take-precedence behavior. I'll be sure to spell out that behavior in the updated docs.

@zslayton zslayton merged commit be01786 into master Feb 20, 2020
@zslayton zslayton added this to the M4 milestone Mar 5, 2020
@zslayton zslayton deleted the zslayton-load-dom branch March 27, 2020 20:30
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