The problem
Need to normalize complex nested data for efficient state management
The solution
function normalizeData(data, schema) {
const result = {
entities: {},
result: []
};
function normalize(item, path = '') {
const normalized = { ...item };
const entityKey = schema[path]?.key || path || 'default';
if (!result.entities[entityKey]) {
result.entities[entityKey] = {};
}
// Handle nested objects/arrays based on schema
Object.keys(normalized).forEach(key => {
const fullPath = path ? `${path}.${key}` : key;
const fieldSchema = schema[fullPath];
if (fieldSchema) {
if (fieldSchema.type === 'array' && Array.isArray(normalized[key])) {
// Normalize array items
const itemIds = normalized[key].map((item, index) => {
const itemPath = `${fullPath}[]`;
const nestedSchema = { ...schema, [itemPath]: fieldSchema.itemSchema };
const normalizedItem = normalize(item, itemPath);
return normalizedItem.id;
});
normalized[key] = itemIds;
} else if (fieldSchema.type === 'object' && normalized[key] && typeof normalized[key] === 'object') {
// Normalize nested object
const nestedItem = normalize(normalized[key], fullPath);
normalized[key] = nestedItem.id;
}
}
});
// Store in entities
if (normalized.id) {
result.entities[entityKey][normalized.id] = normalized;
}
return normalized;
}
if (Array.isArray(data)) {
result.result = data.map(item => {
const normalized = normalize(item);
return normalized.id;
});
} else {
const normalized = normalize(data);
result.result = normalized.id;
}
return result;
}
// Denormalize helper
function denormalizeData(normalizedData, schema) {
const { entities, result } = normalizedData;
function denormalize(id, entityKey) {
const entity = entities[entityKey][id];
if (!entity) return null;
const denormalized = { ...entity };
Object.keys(denormalized).forEach(key => {
const value = denormalized[key];
if (Array.isArray(value)) {
// Check if array contains IDs that need denormalization
const firstId = value[0];
const possibleEntityKey = Object.keys(entities).find(
key => entities[key][firstId]
);
if (possibleEntityKey) {
denormalized[key] = value.map(id =>
denormalize(id, possibleEntityKey)
);
}
} else if (typeof value === 'string' || typeof value === 'number') {
// Check if this is an ID reference
const possibleEntityKey = Object.keys(entities).find(
key => entities[key][value]
);
if (possibleEntityKey) {
denormalized[key] = denormalize(value, possibleEntityKey);
}
}
});
return denormalized;
}
if (Array.isArray(result)) {
const entityKey = Object.keys(entities)[0];
return result.map(id => denormalize(id, entityKey));
}
const entityKey = Object.keys(entities)[0];
return denormalize(result, entityKey);
}Parameters
dataobject|arrayData to normalize
schemaobjectNormalization schema definition
Put it to work
const blogData = [
{
id: 1,
title: 'Post 1',
author: {
id: 101,
name: 'John Doe',
email: '[email protected]'
},
comments: [
{ id: 1001, text: 'Great post!', user: { id: 201, name: 'Alice' } },
{ id: 1002, text: 'Nice!', user: { id: 202, name: 'Bob' } }
]
}
];
const schema = {
'author': { type: 'object', key: 'users' },
'comments': { type: 'array', key: 'comments', itemSchema: { type: 'object' } },
'comments[].user': { type: 'object', key: 'users' }
};
// Normalize data
const normalized = normalizeData(blogData, schema);
console.log(normalized.entities);
// {
// default: { '1': { id: 1, title: 'Post 1', author: 101, comments: [1001, 1002] } },
// users: { '101': { id: 101, name: 'John Doe', email: '[email protected]' }, ... },
// comments: { '1001': { id: 1001, text: 'Great post!', user: 201 }, ... }
// }
// Denormalize back
const original = denormalizeData(normalized, schema);
console.log(original[0].author.name); // 'John Doe'