Redux normalizr - nested API responses
Asked Answered
D

2

8

How can I use normalizr to deal with nested standardised JSON API responses that are key via the { data: ... } standard?

For example a Book

{
    data: {
        title: 'Lord of the Rings',
        pages: 9250,
        publisher: {
            data:  {
                name: 'HarperCollins LLC',
                address: 'Big building next to the river',
                city: 'Amsterdam'
            },
        },
        author: {
            data: {
                name: 'J.R.R Tolkien',
                country: 'UK',
                age: 124,
            }
        }
    }
}   

How would I design schemas to deal with the nested data key?

Disseminate answered 1/7, 2016 at 13:53 Comment(2)
The json which you post doesn't follow the JSONAPI specifications. Quickly, you can not nest other resources in the main one, but for including other resources in the payload you've to use the specific key include. In the main resource you can send only a limited set of information about the relationships between itself and other resources under the key relationships .Busby
Your right, I will update the questionDisseminate
S
3

I believe what you're after is the use of the assignEntity function which can be passed in the options of normalize. In this instance it lets us, where appropriate, filter out the redundant data properties and go straight to the values underneath.

Effectively assignEntity let's you control how each key of data is normalized. Take a look here for a little more on how it works.

I put this together as a demonstration, take a look: http://requirebin.com/?gist=b7d89679202a202d72c7eee24f5408b6. Here's a snippet:

book.define({
  data: {
    publisher: publisher,
    author: author,
    characters: normalizr.arrayOf(character)
  }}
);

publisher.define({
  data: {
    country: country
  }
});

const result = normalizr.normalize(response, book, { assignEntity: function (output, key, value, input) {
  if (key === 'data') {
    Object.keys(value).forEach(function(d){
      output[d] = value[d];
    })
  } else {
    output[key] = value;
  }
}});

Also see in particular Ln 29, where the array of characters has some objects with the information nested within data and some without. All are normalized correctly.

I also added some parts to show how it works with arrays and deeply nested data, see the country model within publisher.

With the data provided you will need a slug due to the absence of id's, which each schema also contains in the example.

Normalizr is fantastic, I hope that helps explain a little more about it :)

Slovene answered 4/7, 2016 at 16:58 Comment(12)
Great answer, awesome example!Disseminate
Thanks! Let me know if you think any part could benefit from further explanation :)Slovene
I'm going to update the question with a proper JSON-API responseDisseminate
Yes, very much struggling with this im getting a subkey of undefined for all the nested characters, publishers etc... the only change i made to your example was i actually have an { id } key in my entities... SEE: requirebin.com/?gist=db7d88f9866ff59aad62e01e6b6d630bDisseminate
Because your id property is nested within the data object you need to tell normalizr to specifically check in there to find it. Check this out: requirebin.com/?gist=26b016c32f130c8b23024ef045e9d221 :)Slovene
Thank you :-) although my use case is slightly different... arrayOf items are always keyed with { data } for example { characters: data: [...] } your help is very very appreciated. Cant seem to crack this oneDisseminate
Then within your define, write: book.define({ data: { publisher: publisher, author: author, characters: { data: normalizr.arrayOf(character) }}} );Slovene
haha so obvious... Only now the value inside entities.books.characters is an object and not an array? is that normal and okay? all the docs and your examples list it as an array....Disseminate
I updated the Requirebin example again. This is occuring because of the assignEntity function processes values with the data key as objects. You can skip over it by checking if it's an Array (as shown), that's probably the easiest way :)Slovene
This is getting out of hand :-) that does not work as expected. Its not possible (maybe) to assign an array to output normalizr seems to ignore this. I have engineered the bin example: requirebin.com/?gist=76f79fb6cd4990a4c88131fb4b151958 I will update my answer when we can crack this...Disseminate
Yea normalizr is working correct in that regard, since it doesn't know that you don't have any other keys aside from data in the characters object, so it won't let the whole thing be overwritten with as an array. It could be worth instead running the object through a sort of data-property flattening function first, one that squashes the data key out when it sees it's the only key of the nested object (and switches the object to an array if that's what the data key is). And then normalizing that (which would be much simpler)Slovene
That was also my thoughts... Running an arbitrary pre/post normalisation function. Although in the end as I control my API it's now fully normalised server side. Better. Less expensive on the client, better the CPU you know the the one you don't. That being said I will come up with an answer. Thank you so much for your efforts, very appreciated !!Disseminate
A
6

For each entity in your response, you should create it's own schema.

In your example, we have three entities - books, authors and publishers:

// schemas.js
import { Schema } from 'normalizr';

const bookSchema = new Schema('book');
const publisherSchema = new Schema('publisher');
const authorSchema = new Schema('author');

If some entity contains nested data which should be normalized, we need to use define method of it schema.This method accepts an object with nesting rules.

If we need to normalize publisher and author props of book entity, we should pass an object to define function with same structure as our response:

// schemas.js
bookSchema.define({
  data: {
    publisher: publisherSchema,
    author: authorSchema
  }
});

Now we can normalize our response:

import { normalize } from 'normalizr';
import { bookSchema } from './schemas.js';

const response = {
    data: {
        title: 'Lord of the Rings',
        pages: 9250,
        publisher: {
            data:  {
                name: 'HarperCollins LLC',
                address: 'Big building next to the river',
                city: 'Amsterdam'
            },
        },
        author: {
            data: {
                name: 'J.R.R Tolkien',
                country: 'UK',
                age: 124,
            }
        }
    }
}

const data = normalize(response, bookSchema);
Aludel answered 1/7, 2016 at 14:24 Comment(3)
Okay, if you could elaborate. Sounds great. Would like a well explained answer for the rest of the community.Disseminate
@Disseminate no problem :) Updated answer.Aludel
What if (as above) the root response object, book in this case is also nested with { data } ?Disseminate
S
3

I believe what you're after is the use of the assignEntity function which can be passed in the options of normalize. In this instance it lets us, where appropriate, filter out the redundant data properties and go straight to the values underneath.

Effectively assignEntity let's you control how each key of data is normalized. Take a look here for a little more on how it works.

I put this together as a demonstration, take a look: http://requirebin.com/?gist=b7d89679202a202d72c7eee24f5408b6. Here's a snippet:

book.define({
  data: {
    publisher: publisher,
    author: author,
    characters: normalizr.arrayOf(character)
  }}
);

publisher.define({
  data: {
    country: country
  }
});

const result = normalizr.normalize(response, book, { assignEntity: function (output, key, value, input) {
  if (key === 'data') {
    Object.keys(value).forEach(function(d){
      output[d] = value[d];
    })
  } else {
    output[key] = value;
  }
}});

Also see in particular Ln 29, where the array of characters has some objects with the information nested within data and some without. All are normalized correctly.

I also added some parts to show how it works with arrays and deeply nested data, see the country model within publisher.

With the data provided you will need a slug due to the absence of id's, which each schema also contains in the example.

Normalizr is fantastic, I hope that helps explain a little more about it :)

Slovene answered 4/7, 2016 at 16:58 Comment(12)
Great answer, awesome example!Disseminate
Thanks! Let me know if you think any part could benefit from further explanation :)Slovene
I'm going to update the question with a proper JSON-API responseDisseminate
Yes, very much struggling with this im getting a subkey of undefined for all the nested characters, publishers etc... the only change i made to your example was i actually have an { id } key in my entities... SEE: requirebin.com/?gist=db7d88f9866ff59aad62e01e6b6d630bDisseminate
Because your id property is nested within the data object you need to tell normalizr to specifically check in there to find it. Check this out: requirebin.com/?gist=26b016c32f130c8b23024ef045e9d221 :)Slovene
Thank you :-) although my use case is slightly different... arrayOf items are always keyed with { data } for example { characters: data: [...] } your help is very very appreciated. Cant seem to crack this oneDisseminate
Then within your define, write: book.define({ data: { publisher: publisher, author: author, characters: { data: normalizr.arrayOf(character) }}} );Slovene
haha so obvious... Only now the value inside entities.books.characters is an object and not an array? is that normal and okay? all the docs and your examples list it as an array....Disseminate
I updated the Requirebin example again. This is occuring because of the assignEntity function processes values with the data key as objects. You can skip over it by checking if it's an Array (as shown), that's probably the easiest way :)Slovene
This is getting out of hand :-) that does not work as expected. Its not possible (maybe) to assign an array to output normalizr seems to ignore this. I have engineered the bin example: requirebin.com/?gist=76f79fb6cd4990a4c88131fb4b151958 I will update my answer when we can crack this...Disseminate
Yea normalizr is working correct in that regard, since it doesn't know that you don't have any other keys aside from data in the characters object, so it won't let the whole thing be overwritten with as an array. It could be worth instead running the object through a sort of data-property flattening function first, one that squashes the data key out when it sees it's the only key of the nested object (and switches the object to an array if that's what the data key is). And then normalizing that (which would be much simpler)Slovene
That was also my thoughts... Running an arbitrary pre/post normalisation function. Although in the end as I control my API it's now fully normalised server side. Better. Less expensive on the client, better the CPU you know the the one you don't. That being said I will come up with an answer. Thank you so much for your efforts, very appreciated !!Disseminate

© 2022 - 2024 — McMap. All rights reserved.