JavaScripthardJavaScript

Data Normalizer

Normalize nested data structures into flat, relational format

01

The problem

Need to normalize complex nested data for efficient state management

02

The solution

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

03

Parameters

dataobject|array

Data to normalize

schemaobject

Normalization schema definition

04

Put it to work

Example
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'