Search code examples
arraysjsonfunctional-programmingjson-apiramda.js

rearranging JSON with ramda.js - lens based on sibling element?


Here is my data structure (abbreviated, and you can skip over this until you get to the heart of my question below):

  var data = {
  jsonapi: { version: '1.0' },
  data: {
    type: 'mobile_screens',
    id: '1',
    attributes: { title: 'Watch' },
    relationships: {
      mobile_screen_components: {
        data: [
          { type: 'mobile_screen_components', id: '1_1' },
          { type: 'mobile_screen_components', id: '1_2' },
        ],
      },
    },
  },
  included: [
    {
      type: 'videos',
      id: '5590720024001',
      attributes: {
        thumbnail: {
          width: 1280,
          url:
            'http://media2.XXXXX.com/BrightCove/694940094001/2017/09/27/694940094001_5590729403001_5590720024001-vs.jpg?pubId=694940094001',
          height: 720,
        },
        start_air_date: '2017-09-27T16:32:00.000Z',
        streams: [
          {
            url:
              'http://XXXXX-f.akamaihd.net/i/BrightCove/694940094001/2017/09/27/694940094001_55907,27023001_5590720024001,30873001_5590720024001,32416001_5590720024001,32423001_5590720024001,32792001_5590720024001,32896001_5590720024001,.mp4.csmil/master.m3u8',
            mime_type: 'MP4',
          },
        ],
        last_modified_date: '2017-09-27T16:50:13.471Z',
        description: 'House Republicans renew call amid fresh Comey concerns',
      },
    },
    {
      type: 'videos',
      id: '5590670324001',
      attributes: {
        ...
      },
    },
    {
      type: 'videos',
      id: '5590665282001',
      attributes: {
        ...
      },
    },
    {
      id: '20fb845a-72ad-4d5f-a27a-c01efd857f76',
      type: 'articles',
      attributes: {
        fn__legacy_type: 'articles',
        title: 'Restaurant plans ginormous site near Wall Street, Ground Zero',
        fn__media_tags: ['business'],
        last_published_date: '2017-09-20T18:20:29.526Z',
        thumbnail: {
          image: {
            url:
              'https://secure.media.foxnews.com/BrightCove/990505083001/990505083001/2017/09/13/990505083001_5575086468001_5574853539001-vs.jpg?pubId=694940094001',
          },
        },
      },
      relationships: {
        components_relationships: {
          data: [
            {
              id: 'article_id_7',
              type: 'videos',
            },
            {
              id: 'article_id_8',
              type: 'videos',
            },
          ],
        },
      },
    },
    {
      id: 'e6e327ec-ebb2-4220-a391-41f798e654f7',
      type: 'articles',
      attributes: {
        ...
      },
      relationships: {
        ...
      },
    },
    {
      type: 'mobile_screen_components',
      id: '1_1',
      attributes: { display_type: 'shelf', title: 'Featured Playlist' },
      relationships: {
        videos: {
          data: [
            [
              { type: 'videos', id: '5590720024001' },
              { type: 'videos', id: '5590670324001' },
              { type: 'videos', id: '5590665282001' },
              {
                id: '20fb845a-72ad-4d5f-a27a-c01efd857f76',
                type: 'articles',
              },
              {
                id: 'e6e327ec-ebb2-4220-a391-41f798e654f7',
                type: 'articles',
              },
          ],
        },
        articles: {
          data: [
              {
                id: '20fb845a-72ad-4d5f-a27a-c01efd857f76',
                type: 'articles',
              },
              {
                id: 'e6e327ec-ebb2-4220-a391-41f798e654f7',
                type: 'articles',
              },
          ],
        }
      },
    },
    {
      type: 'videos',
      id: '1241186546001',
      attributes: {
        ...
      },
    },
    {
      type: 'videos',
      id: '1251429410001',
      attributes: {
        ...
      },
    },
    {
      type: 'mobile_screen_components',
      id: '1_2',
      attributes: { display_type: 'shelf', title: 'Live TV' },
      relationships: {
        videos: {
          data: [
            { type: 'videos', id: '1241186546001' },
            { type: 'videos', id: '1251429410001' },
          ],
        },
      },
    },
    {
      type: 'videos',
      id: '2013931500001',
      attributes: {
        ...
      },

    },
    {
      type: 'mobile_screen_components',
      id: '1_3',
      attributes: { display_type: 'shelf', title: 'Live Streams' },
      relationships: {
        videos: {
          data: [{ type: 'videos', id: '2013931500001' }],
        },
      },
    },
  ],
};

For each object in the included array, I would like to check of it has a type of "mobile_screen_components." If it does, I would like to grab all the objects inside that parent object's relationship property (e.g. videos, articles, shows, etc.) and grab each respective data array. Finally, all these arrays would be concatenated, and place under a new data array, which will be a property of an items array, which is now the sole property of the original object.

That is to say that

{
      type: 'mobile_screen_components',
      id: '1_1',
      attributes: { display_type: 'shelf', title: 'Featured Playlist' },
      relationships: {
        videos: {
          data: [
            [
              { type: 'videos', id: '5590720024001' },
              { type: 'videos', id: '5590670324001' },
              { type: 'videos', id: '5590665282001' }
          ]
        },
        articles: {
          data: [
              {
                id: '20fb845a-72ad-4d5f-a27a-c01efd857f76',
                type: 'articles',
              },
              {
                id: 'e6e327ec-ebb2-4220-a391-41f798e654f7',
                type: 'articles',
              },
          ],
        }
      },
    }

would become

{
      type: 'mobile_screen_components',
      id: '1_1',
      attributes: { display_type: 'shelf', title: 'Featured Playlist' },
      relationships: {
        items: {
          data: [
            [
              { type: 'videos', id: '5590720024001' },
              { type: 'videos', id: '5590670324001' },
              { type: 'videos', id: '5590665282001' },
              {
                id: '20fb845a-72ad-4d5f-a27a-c01efd857f76',
                type: 'articles',
              },
              {
                id: 'e6e327ec-ebb2-4220-a391-41f798e654f7',
                type: 'articles',
              },
            ],
          ],
        },
      },
    }

I want to use a lens as to return to whole of the structure with just this transformation applied. My initial stab at this does not work:

const getMCRel = (type, rels) => {
  if (type === 'mobile_screen_components' ) {
    return rels
  }
  return undefined
} 
const findMCRel = R.compose(getMCRel, R.props(['type','relationships']));
const rLens = R.lens(findMCRel, R.assoc('relationships'));
const itemsWrapper = (arr) => ({"items": arr});
const result = R.map(R.over(rLens,R.compose(itemsWrapper, R.values)),data.included)

Solution

  • This doesn't get you all of the way, but for the conversion of the "mobile_screen_components" part, you could use something like this:

    const simplify = over(
      lensProp('relationships'), 
      pipe(pluck('data'), values, flatten, objOf('data'), objOf('items'))
    )
    
    simplify(stuff)
    

    You can see this in action in the Ramda REPL.

    And to test if your item is of the correct type, you simply use:

    propEq('type', 'mobile_screen_component')
    

    But your requirements are not clear enough to me to know if you want to reject those that are not of the right type or whether you want to map and apply that function only when this predicate is true.

    Update

    Trying to work your full requirements, as much as I understand them, you might try something like:

    const simplifyAll = over(
      lensProp('included'), 
      map(when(propEq('type', 'mobile_screen_components'), simplify))
    )
    
    simplifyAll(data)
    

    This leaves a lot of undefineds in many of the relationships/items/data lists. If you want to get rid of them, you might update simplify to add reject(isNil) after flatten:

    const simplify = over(
      lensProp('relationships'), 
      pipe(pluck('data'), values, flatten, reject(isNil), objOf('data'), objOf('items'))
    )
    

    You can see this version too in the Ramda REPL.

    Finally, if this is the only use of the simplify function, you could move it inline. I would recommend against it, as the resulting wall of text is unlikely to be as readable as the above version. But it's your call.