You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Normalizr转换JSON数组,实现双ID快速查询费用

如何构建支持双维度查询的旅行费用数据结构

我有一组旅行费用的JSON数组,想要转换成既能快速通过travelExpenseId查询单条费用,又能通过tripId批量查询对应行程下所有费用的结构。目前我用Normalizr的Schema只实现了按travelExpenseId索引的功能,希望能新增expensesByTripId实体来实现按行程ID查询的需求。

原始费用数据

[ 
  { 
    "travelExpenseId":11, 
    "tripId":2, 
    "paymentPurpose":"some payment purpose 2", 
    "receiptNumber":"EF12312_2", 
    "receiptDate":"2018-09-30T00:00:00", 
    "receiptPrice":107000.0, 
    "receiptCurrency":"руб." 
  }, 
  { 
    "travelExpenseId":10, 
    "tripId":2, 
    "paymentPurpose":"some payment purpose 1", 
    "receiptNumber":"EF12312_1", 
    "receiptDate":"2018-09-30T00:00:00", 
    "receiptPrice":107000.0, 
    "receiptCurrency":"руб." 
  } 
]

当前使用的Normalizr Schema

export const expenseSchema = new schema.Entity('expenses', {}, { idAttribute: 'travelExpenseId' }); 
export const expensesListSchema = [expenseSchema];

当前转换结果

data: { 
  entities: { 
    expenses: { 
      '10': { 
        travelExpenseId: 10, 
        tripId: 2, 
        paymentPurpose: 'some payment purpose 1', 
        receiptNumber: 'EF12312_1', 
        receiptDate: '2018-09-30T00:00:00', 
        receiptPrice: 107000, 
        receiptCurrency: 'руб.' 
      }, 
      '11': { 
        travelExpenseId: 11, 
        tripId: 2, 
        paymentPurpose: 'some payment purpose 2', 
        receiptNumber: 'EF12312_2', 
        receiptDate: '2018-09-30T00:00:00', 
        receiptPrice: 107000, 
        receiptCurrency: 'руб.' 
      } 
    } 
  }, 
  result: [ 11, 10 ] 
}

期望的转换结果

data: { 
  entities: { 
    expensesByTripId: { 
      '2': [10, 11], 
    }, 
    expenses: { 
      '10': { 
        travelExpenseId: 10, 
        tripId: 2, 
        paymentPurpose: 'some payment purpose 1', 
        receiptNumber: 'EF12312_1', 
        receiptDate: '2018-09-30T00:00:00', 
        receiptPrice: 107000, 
        receiptCurrency: 'руб.' 
      }, 
      '11': { 
        travelExpenseId: 11, 
        tripId: 2, 
        paymentPurpose: 'some payment purpose 2', 
        receiptNumber: 'EF12312_2', 
        receiptDate: '2018-09-30T00:00:00', 
        receiptPrice: 107000, 
        receiptCurrency: 'руб.' 
      } 
    } 
  }, 
  result: [ 11, 10 ] 
}

解决方案

下面提供两种可行的方案,一种基于你正在使用的Normalizr扩展,另一种是手动转换的轻量方案:

方案一:扩展Normalizr实现双维度索引

Normalizr本身没有直接支持多维度分组的API,但我们可以通过两种方式补充这个功能:

方式1:normalize后手动构建分组

这是最直观的方式,先完成原有Normalizr的转换,再遍历结果生成expensesByTripId:

import { normalize } from 'normalizr';

// 你的原有Schema
export const expenseSchema = new schema.Entity('expenses', {}, { idAttribute: 'travelExpenseId' });
export const expensesListSchema = [expenseSchema];

// 原始费用数据
const rawExpenses = [/* 你的原始JSON数组 */];

// 执行原有normalize流程
const normalizedData = normalize(rawExpenses, expensesListSchema);

// 构建tripId到expenseId的映射
const expensesByTripId = {};
Object.values(normalizedData.entities.expenses).forEach(expense => {
  const tripIdStr = expense.tripId.toString();
  if (!expensesByTripId[tripIdStr]) {
    expensesByTripId[tripIdStr] = [];
  }
  expensesByTripId[tripIdStr].push(expense.travelExpenseId);
});

// 将映射合并到entities中
normalizedData.entities.expensesByTripId = expensesByTripId;

// 现在normalizedData就是你想要的结构

方式2:利用processStrategy在转换时同步构建分组

如果你想在Normalizr处理实体的过程中就完成分组,可以使用processStrategy钩子,不过需要注意维护一个临时的映射对象:

import { schema, normalize } from 'normalizr';

// 临时存储tripId到expenseId的映射
const tripExpenseMap = {};

const expenseSchema = new schema.Entity('expenses', {}, {
  idAttribute: 'travelExpenseId',
  // 处理每个expense时同步更新映射
  processStrategy: (expense) => {
    const tripIdStr = expense.tripId.toString();
    if (!tripExpenseMap[tripIdStr]) {
      tripExpenseMap[tripIdStr] = [];
    }
    tripExpenseMap[tripIdStr].push(expense.travelExpenseId);
    return expense;
  }
});

const expensesListSchema = [expenseSchema];

const rawExpenses = [/* 你的原始JSON数组 */];
const normalizedData = normalize(rawExpenses, expensesListSchema);

// 将映射添加到entities
normalizedData.entities.expensesByTripId = tripExpenseMap;

方案二:手动转换数据(不依赖Normalizr)

如果你的场景不需要Normalizr的其他复杂功能,手动转换会更轻量、灵活:

const rawExpenses = [/* 你的原始JSON数组 */];

const transformedData = {
  entities: {
    expenses: {},
    expensesByTripId: {}
  },
  result: []
};

rawExpenses.forEach(expense => {
  // 按travelExpenseId存储单条费用
  transformedData.entities.expenses[expense.travelExpenseId] = expense;
  
  // 按tripId分组存储expenseId
  const tripIdStr = expense.tripId.toString();
  if (!transformedData.entities.expensesByTripId[tripIdStr]) {
    transformedData.entities.expensesByTripId[tripIdStr] = [];
  }
  transformedData.entities.expensesByTripId[tripIdStr].push(expense.travelExpenseId);
  
  // 保留原始数据的顺序
  transformedData.result.push(expense.travelExpenseId);
});

// transformedData就是你期望的最终结构

内容的提问来源于stack exchange,提问作者AuthorProxy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:52:49