GithubHelp home page GithubHelp logo

jmespath.jep's Introduction

JMESPath

image

JMESPath (pronounced "james path") allows you to declaratively specify how to extract elements from a JSON document.

For example, given this document:

{"foo": {"bar": "baz"}}

The jmespath expression foo.bar will return "baz".

JMESPath also supports:

Referencing elements in a list. Given the data:

{"foo": {"bar": ["one", "two"]}}

The expression: foo.bar[0] will return "one". You can also reference all the items in a list using the * syntax:

{"foo": {"bar": [{"name": "one"}, {"name": "two"}]}}

The expression: foo.bar[*].name will return ["one", "two"]. Negative indexing is also supported (-1 refers to the last element in the list). Given the data above, the expression foo.bar[-1].name will return "two".

The * can also be used for hash types:

{"foo": {"bar": {"name": "one"}, "baz": {"name": "two"}}}

The expression: foo.*.name will return ["one", "two"].

Installation

You can install JMESPath from pypi with:

pip install jmespath

API

The jmespath.py library has two functions that operate on python data structures. You can use search and give it the jmespath expression and the data:

>>> import jmespath
>>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}})
'baz'

Similar to the re module, you can use the compile function to compile the JMESPath expression and use this parsed expression to perform repeated searches:

>>> import jmespath
>>> expression = jmespath.compile('foo.bar')
>>> expression.search({'foo': {'bar': 'baz'}})
'baz'
>>> expression.search({'foo': {'bar': 'other'}})
'other'

This is useful if you're going to use the same jmespath expression to search multiple documents. This avoids having to reparse the JMESPath expression each time you search a new document.

Options

You can provide an instance of jmespath.Options to control how a JMESPath expression is evaluated. The most common scenario for using an Options instance is if you want to have ordered output of your dict keys. To do this you can use either of these options:

>>> import jmespath
>>> jmespath.search('{a: a, b: b}',
...                 mydata,
...                 jmespath.Options(dict_cls=collections.OrderedDict))


>>> import jmespath
>>> parsed = jmespath.compile('{a: a, b: b}')
>>> parsed.search(mydata,
...               jmespath.Options(dict_cls=collections.OrderedDict))

Custom Functions

The JMESPath language has numerous built-in functions, but it is also possible to add your own custom functions. Keep in mind that custom function support in jmespath.py is experimental and the API may change based on feedback.

If you have a custom function that you've found useful, consider submitting it to jmespath.site and propose that it be added to the JMESPath language. You can submit proposals here.

To create custom functions:

  • Create a subclass of jmespath.functions.Functions.
  • Create a method with the name _func_<your function name>.
  • Apply the jmespath.functions.signature decorator that indicates the expected types of the function arguments.
  • Provide an instance of your subclass in a jmespath.Options object.

Below are a few examples:

import jmespath
from jmespath import functions

# 1. Create a subclass of functions.Functions.
#    The function.Functions base class has logic
#    that introspects all of its methods and automatically
#    registers your custom functions in its function table.
class CustomFunctions(functions.Functions):

    # 2 and 3.  Create a function that starts with _func_
    # and decorate it with @signature which indicates its
    # expected types.
    # In this example, we're creating a jmespath function
    # called "unique_letters" that accepts a single argument
    # with an expected type "string".
    @functions.signature({'types': ['string']})
    def _func_unique_letters(self, s):
        # Given a string s, return a sorted
        # string of unique letters: 'ccbbadd' ->  'abcd'
        return ''.join(sorted(set(s)))

    # Here's another example.  This is creating
    # a jmespath function called "my_add" that expects
    # two arguments, both of which should be of type number.
    @functions.signature({'types': ['number']}, {'types': ['number']})
    def _func_my_add(self, x, y):
        return x + y

# 4. Provide an instance of your subclass in a Options object.
options = jmespath.Options(custom_functions=CustomFunctions())

# Provide this value to jmespath.search:
# This will print 3
print(
    jmespath.search(
        'my_add(`1`, `2`)', {}, options=options)
)

# This will print "abcd"
print(
    jmespath.search(
        'foo.bar | unique_letters(@)',
        {'foo': {'bar': 'ccbbadd'}},
        options=options)
)

Again, if you come up with useful functions that you think make sense in the JMESPath language (and make sense to implement in all JMESPath libraries, not just python), please let us know at jmespath.site.

Specification

If you'd like to learn more about the JMESPath language, you can check out the JMESPath tutorial. Also check out the JMESPath examples page for examples of more complex jmespath queries.

The grammar is specified using ABNF, as described in RFC4234. You can find the most up to date grammar for JMESPath here.

You can read the full JMESPath specification here.

Testing

In addition to the unit tests for the jmespath modules, there is a tests/compliance directory that contains .json files with test cases. This allows other implementations to verify they are producing the correct output. Each json file is grouped by feature.

Discuss

Join us on our Gitter channel if you want to chat or if you have any questions.

jmespath.jep's People

Contributors

jamesls avatar springcomp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

jmespath.jep's Issues

Spec change - merge() function

Background

In all the example given in the spec page for the merge() functionLITERAL types are provided as arguments to the function. While this demonstrates the behaviour it is not at all useful in practical terms. Literals can only be explicitly defined by the editor of an expression. Much more useful would be to merge on results of expressions or on projections.

Examples:

search([{a: "AY", x: "EX"}, {a: "EY", b: "BEE"}], 'merge(@)')
// OUTPUTS: {"a": "EY", "b": "BEE", "x": "EX"}

search([{a: "AY", x: "EX"}, {a: "EY", b: "BEE"}], 'merge([{FED: @[0].x}, {BAR: @[1].b}])')
// OUTPUTS: {"BAR": "BEE", "FED": "EX"}

In order to do this merge would have to accept either TYPE_OBJECT or TYPE_ARRAY to work and the function would need to be able to switch on either type to handle this.

Discussion

Array can contain any value and ideally we may want to restrict it to a new TYPE_ARRAY_OBJECT type so that we can guarantee only objects can be used in the merge. One interesting side effect of this in javascript is that merging arrays results in array members being keyed in their index. This means we can do the following with very little code change:

search([{"A": "ONE"}, "TWO", {"C": "THREE"}], 'merge(@)')
// OUTPUTS: {"0": "ZERO", "A": "ONE", "C": "THREE"}

// With all JSON array member types:

search([{"A": "ONE"}, true, null, "BAR", ["ZERO"], 666], 'merge(@)')
// OUTPUTS: {"0": "ZERO", "1": "A", "2": "R", "A": "ONE"}

Implementation in jmespath.ts

// Runtime
  private functionMerge: RuntimeFunction<JSONObject[], JSONObject> = resolvedArgs => {
    let merged = {};
    for (let i = 0; i < resolvedArgs.length; i += 1) {
      const current = resolvedArgs[i];
      if (Array.isArray(current)) {
        merged = Object.assign(merged, ...current);
      } else {
        merged = Object.assign(merged, current);
      }
    }
    return merged;
  };

// and in the function table

    merge: {
      _func: this.functionMerge,
      _signature: [
        {
          types: [InputArgument.TYPE_OBJECT, InputArgument.TYPE_ARRAY],
          variadic: true,
        },
      ],
    },

Allow reference to external parameter

Current expression logic allow access to attributes of the current node (or the current node itself via @). The request is to formalize access to a second object of “external parameter”, similar in spirit to the ability to parametrized jsonpath queries in PostgreSQL.

This should allow:
Employee.[[email protected])) // using special node reference

Employee.[?name=param(&name)) // With a function, or

Making it much easier to construct queries with dynamic parameters. Similar to placeholders in various sql dialect

JEP Syntax Policy

One of the qualities I enjoy of JMESPath is its syntactic simplicity.
I have seen a number of proposals to introduce new syntax for various purposes that has me concerned.
One example is adding a regex operator.
I would like to propose that operators and syntax be reserved for transforming, referencing, and subscripting data structures and not data values. All value specific operations should be implemented as functions. The exception to this would be arithmetic operators intended to operate on values.
This means all string operations other than those that could be reasonably described by an arithmetic operator should be implemented as a function.

Having complex value operations described as functions makes JMESPaths more readable.

Function proposal - format()

Given a template and either an array or object output a string interpolated with values:

For example in Javascript one might implement it as follows:

registerFunction(
  'format',
  ([template, templateStringsMap]) => {
    let newTemplate = template;
    for (const attr in templateStringsMap) {
      const rgx = new RegExp(`\\$\\{${attr}\\}`, 'g');
      newTemplate = newTemplate.replace(rgx, templateStringsMap[attr] ?? '');
    }
    return newTemplate;
  },
  [{ types: [TYPE_STRING] }, { types: [TYPE_OBJECT, TYPE_ARRAY] }],
);

Examples:

// With ARRAY input:
jmespath.search(
    [1,"2", null, ["a", 2, "foo"], {b: "z"}, true, false],
    "format4('${0} | ${1} | ${2} | ${3} | ${4} | ${5} | ${6} | ${7}', @)"
)
// OUTPUTS: '1 | 2 | null | a,2,foo | [object Object] | true | false | ${7}'
// With **OBJECT** input:
jmespath.search(
    {foo: 'FOO', bar: null},
    "format4('${foo} | ${bar} | ${baz}', @)"
);
// OUTPUTS: 'FOO | null | 12 | ${baz}'

Allow string comparison

Suggesting extending the definition of >, <=, <, <= to cover comparison between string. Will address sorting/filtering for many data set with string keys, including dates (iso format), ordering data set by named, etc.

as much as I can tell, implementation effort for each library is minimal.

[Initial Feedback] New `like` and `match` keywords

I have been thinking that comparisons using new like and match keywords would be a natural extension.

The comparator rule would be extended like so:

comparator =/ "like" / "match" 

Are these features that would be of interest?

Like

items[? foo like '**/*.json' ]

The like comparator would match simple SQL-like, wildcard-like or glob-like patterns.

I have investigated adding such contextual keywords to the language and found that it would probably easier with lex/yacc-based implementations, Nevertheless, using the top-down parser approach, one simply has to account for those keywords between the nud() and led() calls in the main parsing loop.

This makes the parsing algorithm less "pure" but I’m pretty sure more knowledgeable people might come up with more elegant designs.

Match

The match comparator would match simple regular expressions.

items[? foo match 'ba[rz]' ]

I know that regular expressions have been a touchy subject in the past but I strongly believe we can make this work for JMESPath with:

  • A narrow focus on matching only, unanchored expressions. That is, the expression returns true or false.
  • Restricting the syntax to a tiny but useful subset of interoperable syntax.

I have come up with a prototype for a reference implementation of a simple push-down automaton-based checker and the implementation is reasonaly tight and compact.

I believe most languages in which a JMESPath implementation exists do indeed support the interoperable subset.
The idea from the standazdization document referred to above is to:

  1. First check that the syntax is valid.
  2. Maps the interoperable regex to one compatible with the target implementation.
  3. Execute the target implementation expression.

The standardization documents lists mappings for ECMAScript, PCRE, RE2, Go and Ruby dialects.

Once relying on such a compact library, the implementation is in fact really easy.

Spec extension - string slice

The spec doesn't explicitly exclude strings from being sliced

Index expressions are defined as

index-expression  = expression bracket-specifier / bracket-specifier

and expression includes current node and raw-string as follows

expression        =/ function-expression / pipe-expression / raw-string
expression        =/ current-node

It should be possible then to do the following and not restrict it to JSON array types only

search("FOOBAR", '@[1:3]')
// OUTPUTS: 'OO'

Let us select dict values by criteria on their associated keys

Let's say I have

{
  'bark': 'oink',
  'foo1': 'foo',
  'foo2': 'woof',
  'foo3': 'meow'
}

and want to select all values for which keys start with "foo". I tried for a bit to get JMESPath to get me ['foo', 'woof', 'meow'] but was unsuccessful. I don't know if it's impossible, but I was thinking that a simple approach might be a built-in function called pivot/reify/mappings (I'm bad at naming) that transforms the above into a list of Key/Value mappings, so:

[
  { 'Key': 'bark', 'Value': 'oink' },
  { 'Key': 'foo1', 'Value': 'foo' },
  { 'Key': 'foo2', 'Value': 'woof' },
  { 'Key': 'foo3', 'Value': 'meow' }
]

which seems far easier to deal with. I'm open to other approaches to achieving the same goal, of course 😄

Spec extension namespace

I would like to propose that the spec explicitly addresses implementation specific functions. Allowing a provision for functions specific to the implementation will free up those communities to expand features and functionality while not risking colliding with future additions to the spec.

I propose a prefix to non-spec functions. This will inform the end user that the function may change or be replaced in the future. Perhaps prefix these function names with an X, _, $, or #. Any custom functions provided by the library implementation or importing application will automatically be prefixed with this character.

Opening up the spec like this will allow innovation and test beds to be created. Non-spec functions that gain popularity can then be evaluated for inclusion into the spec. This will also alleviate any confusion when a custom function is encountered in the wild as it will be made clear that it is not supported by other implementations.

Allow expression in index and slice expression

It is very useful to allow data to refer to itself.
I propose bracket-specifier = "[" (number / "*") "]" / "[]" be extended to bracket-specifier = "[" (expression / "*" ) "]" / "[]" for indexes.

slice-expression = [number] ":" [number] [ ":" [number] ] be extended to slice-expression = [expression] ":" [expression] [ ":" [expression] ] for slices

The scope of the expression is the parent of the object being sliced, this can be extended with let()

[Initial Feedback] Recursive Tree Traversal

A very useful feature of XSLT is to supports recursive tree traversal using XPath.
I believe such a feature would be a great addition to JMESPath.

I have drafted a very ugly working prototype for what could be a new descendant-expression

Specification

A new descendant-expression will be added as an alternative to the expression grammar rule:

descendant = ".." identifier
descendant-expression = ( expression ) 1*( descendant )

expression =/ descendant-expression

A descendant-expression is a projection that returns an array with all the elements of the JSON input with the specified name.

Examples

Given the following JSON input:

{
  "name": "first",
  "top": { "name": "second" },
  "nested": {
    "name": [
      1, "string", false,
      { "name": "third" }
    ]
  }
}

The following expressions are possible:

Expression Result
..name ["first","second",[1,"string",false,{"name":"third"}],"third"]
..name.length(@) [5, 6, 4, 5]
..name..name ["third"]

Compliance Tests

This JEP standardizes a syntax that was formely not valid. For this reason, one of the syntax.json compliance tests must be changed or removed altogether:

    {
      "expression": ".foo",
      "error": "syntax"
    },
    {
      "expression": "foo..bar",
-     "error": "syntax"
+     "result": null
    },

A new file descendant.json will be added to the compliance test suite.

[Proposal] add `error` function

Problem Statement

AFAIK, there is no built-in capability to raise errors from within a JMESpath expression. For example, suppose a JSON document has a status property, and its value is supposed to be up or down. The user wants to map up to 1 and down to 0, which could be done using the following JMESpath expression:

((status == 'up') && `1`) || `0`)

The user may want to detect and raise an error when the input document has a value other than up or down. A workaround is to rely on the fact that some JMESpath expressions are invalid, e.g., to_number('bad') would raise an error.

((status == 'up') && `1`) || ((status == 'down') && `0`) || to_number('bad')

This works but it's very kludgy. In this specific example, one might argue this issue could be validated using a JSON schema, but sometimes there are no JSON schemas.

Proposal

Add a new error function that takes a string expression. If the error function is evaluated, an error is raised with the specified message.

((status == 'up') && `1`) || ((status == 'down') && `0`) || error(join('invalid-value:', [status]))

Additional Information

The spec states errors are raised when problems are encountered during the evaluation process. Currently, the following errors are defined:

  1. invalid-type
  2. unknown-function
  3. invalid-arity

Related: jmespath/jmespath.site#115 , jmespath/jmespath.site#116

Function proposal - entries()

Given a array or object output an array with values indexed on keys:

For example in Javascript one might implement it as follows:

registerFunction(
  'entries',
  ([resolvedArgs]) => {
    return Object.entries(resolvedArgs);
  },
  [{ types: [TYPE_OBJECT, TYPE_ARRAY] }],
);

Examples:

// With ARRAY input:

jmespath.search([1,"2", null, ["a", 2, "foo"], {b: "z"}, true, false], "entries(@)")
// OUTPUTS:   [
//     [ '0', 1 ],
//     [ '1', '2' ],
//     [ '2', null ],
//     [ '3', [ 'a', 2, 'foo' ] ],
//     [ '4', { b: 'z' } ],
//     [ '5', true ],
//     [ '6', false ]
//   ]
// With **OBJECT** input:

jmespath.search({a: 1, b: [2, 3], c: null, 4: {x: 0}}, 'entries(@)')
// OUTPUTS: [
//     [ '4', { x: 0 } ],
//     [ 'a', 1 ],
//     [ 'b', [ 2, 3 ] ],
//     [ 'c', null ]
//   ]

Function proposal - flatMapValues()

For objects, it's currently very difficult (if not impossible) to have access to the key and the value in the same scope of the expression. This function would decompose objects into Array<[key:value]> such that every key corresponds to a decomposed value.

In javascript (typescript) this might look something like:

registerFunction(
  'flatMapValues',
  ([inputObject]) => {
    return Object.entries(inputObject).reduce((flattened, entry) => {
      const [key, value]: [string, any] = entry;
      if (Array.isArray(value)) {
        return [...flattened, ...value.map(v => [key, v])];
      }
      return [...flattened, [key, value]];
    }, [] as any[]);
  },
  [{ types: [TYPE_OBJECT, TYPE_ARRAY] }],
);

Which produces the following:

jmespath.search( { a: [1, 3, 5], b: [2, 4, 6] }, "flatMapValues(@)")
// OUTPUTS: [
//      ['a', 1],
//      ['a', 3],
//      ['a', 5],
//      ['b', 2],
//      ['b', 4],
//      ['b', 6],
//    ]

jmespath.search({ a: [true, { x: 3 }, null, 1234, ['XXX']], b: { x: 2 } }, "flatMapValues(@)")
// OUTPUTS: [
//      ['a', true],
//      ['a', { x: 3 }],
//      ['a', null],
//      ['a', 1234],
//      ['a', ['XXX']],
//      ['b', { x: 2 }],
//    ]

jmespath.search([ [1, 3, 5], [2, 4, 6] ], "flatMapValues(@)")
// OUTPUTS: [
//      ['0', 1],
//      ['0', 3],
//      ['0', 5],
//      ['1', 2],
//      ['1', 4],
//      ['1', 6],
//    ]

These could then be piped to expressions that filter on keys or project keys into new objects/arrays

Function proposal - split()

Given a string - split string on a pattern:

For example in Javascript one might implement it as follows:

registerFunction(
  'split',
  ([splitChar, toSplit]) => {
    return toSplit.split(splitChar);
  },
  [{ types: [TYPE_STRING] }, { types: [TYPE_STRING] }],
);

Examples:

jmespath.search("foo-bar-baz", "split('-', @)")
// OUTPUTS: [ 'foo', 'bar', 'baz' ]

jmespath.search("foo-bar-baz", "split('*', @)")
// OUTPUTS: [ 'foo-bar-baz' ]

Function proposal - parseJson()

Given a string containing embedded json, parse the json, and return the result.

This might be implemented something like:

registerFunction('parseJson', ([json]) => JSON.parse(json), [{types: [TYPE_STRING]}]);

Examples:

jmespath.search("\"true\"", "parseJson(@)")
// OUTPUTS: true

jmespath.search('"{\"a\":1, \"b\":\"c\"}"', "parseJson(@)")
// OUTPUTS {"a":1, "b":"c"}

The pattern of embedding json inside of strings in json is, unfortunately, somewhat common. For example, in AWS APIs.

[Initial feedback] Potential proposal for user-defined function

I’m investigating user-defined lambda-like functions as discussed here.

Is this something of interest to discuss here?

User-defined functions

JMESPath already supports expression-type which are, conceptually, anonymous functions without arguments.

I have toyed with the idea of extending exprefs to support named arguments.
An updated grammar that works looks like this:

expression-type = "&" expression / "<" arguments ">" "=>" expression
arguments = variable-ref *( "," variable-ref

It requires a new token => which is even not absolutely necessary. Parsing involves a new entry in the nud() function to match on token < (less-than-sign).

Reduce function

As expression-type are only supported as function arguments, a reduce()
function could work like this:

reduce(array $array, any $seed, expr [any, any]->any)

Example:

[1, 3, 5, 7]
reduce(@, `1`, <$acc, $cur> => $acc × $cur)

The reduce() function knows how to iterate over its first array argument
and repeatedly create bindings for both function arguments $acc and $cur.

Funnily enough, the lambda function does not support natively the @ current node.
An argument can be made that it could be similar to the $cur argument. In that case,
a choice must be made as to which "context" is specified when evaluating the expref.

Reusing user-defined functions

I then investigated what reusing user-defined functions could look like.
This requires a mechanism to store or hold functions, possibly by name,
as well as a way to call those functions with appropriate parameters.

By taking advantage of the hopefully upcoming let-expression design, one
could bind the anonymous function to a variable reference. Then, syntax for
calling the function using this variable must be extended and supported in
the grammar.

In the following expression:

let $concat = <$lhs, $rhs> => join('-', [$lhs, $rhs])
in  $concat('4', '2')

$concat is bound to the <$lhs, $rhs> => join('-', [$lhs, $rhs]) lambda expression-type. Then, $concat('4', '2') invokes the function indirectly using the $concat variable reference.

The following grammar changes is required:

function-expression = ( unquoted-string / variable-ref )  no-args  / one-or-more-args ) 

Again, implementation is quite easy.

Funnily enough, having also implemented arithmetic-expression grammar rules, I was then able to implement the recursive Fibonacci sequence using the following expression:

let $fib = <$n> => (
      ($n == `0` && `0`) ||
      (($n == `1` && `1`) ||
      ( $fib($n - `1`) + $fib($n - `2`) )
      )
    ) in
      $fib(@)

Root node access

I would like to have access to the data I've passed in within any part of query.

Currently expressions in official implementations, only access to the current node is allowed.

  • Similar in spirit to #33 but only scoped for the initial data.
  • Someone made an implementation like this in https://github.com/daz-is/jmespath.js, where the root node is $. I do not know if it goes against any other part of the spec yet.

Function proposal - groupBy()

Given an array of objects, group the array on a key or expression such that:

// Object array with string
    let returnValue = await applyFunction('fn:jmespath', 'groupBy(@, `a`)', [
      { a: 1, b: 2 },
      { a: 1, b: 3 },
      { a: 2, b: 2 },
      { a: null, b: 999 },
    ]);
    expect(returnValue).toStrictEqual({
      1: [
        { a: 1, b: 2 },
        { a: 1, b: 3 },
      ],
      2: [{ a: 2, b: 2 }],
      null: [{ a: null, b: 999 }],
    });

// Object array with expression reference
    returnValue = await applyFunction('fn:jmespath', 'groupBy(@, &a)', [
      { a: 1, b: 2 },
      { a: 1, b: 3 },
      { a: 2, b: 2 },
      { a: null, b: 999 },
    ]);
    expect(returnValue).toStrictEqual({
      1: [
        { a: 1, b: 2 },
        { a: 1, b: 3 },
      ],
      2: [{ a: 2, b: 2 }],
      null: [{ a: null, b: 999 }],
    });

// Object array with inconsistent schema
    returnValue = await applyFunction('fn:jmespath', 'groupBy(@, &a)', [
      { a: 1, b: 2 },
      { a: 1, b: 3 },
      { b: 4 },
      { a: null, b: 999 },
    ]);
    expect(returnValue).toStrictEqual({
      1: [
        { a: 1, b: 2 },
        { a: 1, b: 3 },
      ],
      null: [{ b: 4 }, { a: null, b: 999 }],
    });

// Inconsistent array member types
    try {
      returnValue = await applyFunction('fn:jmespath', 'groupBy(@, &a)', [
        { a: 1, b: 2 },
        `{ a: 1, b: 3 }`,
        { b: 4 },
        1234,
      ]);
    } catch (error) {
      expect(error.message).toEqualText(
        'TypeError: unexpected type. Expected Array<object> but received Array<object | string | number>',
      );
    }

In javascript (typescript) this might implemented as follows something like:

registerFunction(
  'groupBy',
  function (this: any, [memberList, exprefNode]: [JSONObject[], ExpressionNodeTree | string]) {
    if (!this._interpreter) return {};

    if (typeof exprefNode === 'string') {
      return memberList.reduce((grouped, member) => {
        if (exprefNode in member) {
          const key = member[exprefNode] as string;
          const currentMembers = (grouped[key] as any[]) || [];
          grouped[key] = [...currentMembers, member];
        }
        return grouped;
      }, {});
    }
    const interpreter = this._interpreter;
    const requiredType = Array.from(new Set(memberList.map(member => this.getTypeName(member))));
    const onlyObjects = requiredType.every(x => x === TYPE_OBJECT);
    if (!onlyObjects) {
      throw new Error(
        `TypeError: unexpected type. Expected Array<object> but received Array<${requiredType
          .map(type => this.TYPE_NAME_TABLE[type])
          .join(' | ')}>`,
      );
    }

    return memberList.reduce((grouped, member) => {
      const key = interpreter.visit(exprefNode, member) as string;
      const currentMembers = (grouped[key] as any[]) || [];
      grouped[key] = [...currentMembers, member];
      return grouped;
    }, {});
  },
  [{ types: [TYPE_ARRAY] }, { types: [TYPE_EXPREF, TYPE_STRING] }],
);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.