BSick7 / exjs

Extension library for Array.
MIT License
16 stars 1 forks source link

Traverse JSON for matching @id property #7

Open edsilv opened 8 years ago

edsilv commented 8 years ago

I have a new requirement for the 'manifesto' library:

https://github.com/UniversalViewer/universalviewer/issues/186

I need to be able to traverse an IIIF manifest (using traverseUnique?) to find a node with a given '@id' property. Example manifest:

http://wellcomelibrary.org/iiif/b18035723/manifest

It looks like exjs can do this:

https://github.com/BSick7/exjs/wiki/fromJson

But am I going to need to create mappings for every node type in the IIIF spec? Or can it be generalised somehow? It would be great if it just needed to know that there can be objects with an @id, or there can be arrays of objects with an @id...

BSick7 commented 8 years ago

fromJson automates raw data transformation into a class definition. I added some clarifying documentation to the wiki.

https://github.com/BSick7/exjs/wiki/fromJson#process

edsilv commented 8 years ago

I've made a test app:

https://github.com/edsilv/exjs-test

Test.ts works fine as it's just using your example. However, I'm stuck as to how I should approach Test2.ts.

https://github.com/edsilv/exjs-test/blob/master/test/Test2.ts

I need to be able to find an '@id' matching a given string.

The json I've included is a stripped-down IIIF manifest. The compiler is complaining that .en doesn't exist on an object.

Any pointers greatly appreciated...!

BSick7 commented 8 years ago

en only exists on arrays

Try doing [json2].en()

edsilv commented 8 years ago

Cool, this works:

https://github.com/edsilv/exjs-test/blob/master/test/Test2.ts#L25

for the root-most id. What's the correct method to traverse the child nodes?

edsilv commented 8 years ago

This works:

var result = [json2].en().traverseUnique(x => x.service).first(r => r['@id'] === id);

but it needs to be generic as the child arrays could be named anything.

BSick7 commented 8 years ago

How "generic"? How do you determine what each child array is?

edsilv commented 8 years ago

See this example IIIF file: http://wellcomelibrary.org/iiif/b18035723/manifest

In the sequences array, each sequence has an @id, as does each sequence's rendering, canvas etc.

My example only uses service as an example. I need to be able to find nodes by id for any named array at any depth. Is there a syntax to get exjs to traverse any child array regardless of name? Then my manifesto library can have a "generic" getResourceById method.

BSick7 commented 8 years ago

traverse (and traverseUnique) relies on a flat array of objects to be returned from the functor (x => x.service). This means that as long as you can get all objects into a flat array, traverse will walk those members.

If you have 2 members that are potential arrays, you could return them both as a single array as follows.

var result = [json2].en()
  .traverseUnique(x =>  [].concat(x.canvases || []).concat(x.rendering || []))
  .first(r => r['@id'] === id);

By extrapolating, we could write a helper function to do this for us...

var result = [json2].en()
  .traverseUnique(x => getAllArrays(x))
  .first(r => r['@id'] === id);

function getAllArrays(obj: any): exjs.IEnumerable<any> {
  if (!obj)
    return [].en();
  var all = [].en();
  for (var key in obj) {
    var val = obj[key];
    if (Array.isArray(val))
      all = all.concat(val)
  }
  // flatten all objects into a single array
  return all.selectMany(x => x);
}
edsilv commented 8 years ago

I've added that to the test with this modification:

https://github.com/edsilv/exjs-test/blob/master/test/Test2.ts#L39

If I set a breakpoint there, all.selectMany(function (x) { return x; }).toArray() yields [].

all.toArray() yields an array containing the found object.

Is it ok to exclude the selectMany?

BSick7 commented 8 years ago

Yes, I wrote the selectMany before re-writing the inner for loop. Originally, the purpose of selectMany was to convert a structure like this [ [...n], [...m], [...s] ] to [...n, ...m, ...s].

Removing selectMany is what you will want.