exjs icon indicating copy to clipboard operation
exjs copied to clipboard

Traverse JSON for matching @id property

Open edsilv opened this issue 10 years ago • 10 comments

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...

edsilv avatar Nov 27 '15 11:11 edsilv

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

BSick7 avatar Nov 27 '15 13:11 BSick7

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...!

edsilv avatar Dec 18 '15 18:12 edsilv

en only exists on arrays

Try doing [json2].en()

BSick7 avatar Dec 18 '15 19:12 BSick7

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 avatar Dec 18 '15 20:12 edsilv

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.

edsilv avatar Dec 18 '15 23:12 edsilv

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

BSick7 avatar Dec 29 '15 19:12 BSick7

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.

edsilv avatar Dec 29 '15 19:12 edsilv

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);
}

BSick7 avatar Jan 04 '16 17:01 BSick7

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?

edsilv avatar Jan 05 '16 16:01 edsilv

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.

BSick7 avatar Jan 05 '16 23:01 BSick7