Làm thế nào để tách đối tượng thành đối tượng lồng nhau? (Cách đệ quy)


8

Tôi có một tập dữ liệu chứa tên biến gạch dưới (_). Chẳng hạn như dưới đây:

const data = {
   m_name: 'my name',
   m_address: 'my address',
   p_1_category: 'cat 1',
   p_1_name: 'name 1',
   p_2_category: 'cat 2',
   p_2_name: 'name 2'
}

Tôi muốn chia chúng thành đối tượng / mảng lồng nhau, Dưới đây là kết quả tôi muốn.

{
 m: {
     name: "my name",
     address: "my address"
 },
 p: {
    "1": {category: 'cat 1', name: 'name 1'}, 
    "2": {category: 'cat 2', name: 'name 2'}
 } 
}

Làm thế nào tôi có thể viết một phương pháp đệ quy để đạt được nó thay vì mã hóa cứng? Có lẽ nó nên cho phép xử lý các đối tượng lồng nhau sâu hơn, chẳng hạn như "p_2_one_two_carget: 'value'" vào p: {2: {one: {hai: category: 'value'}}}

var data ={
  m_name: 'my name',
  m_address: 'my address',
  p_1_category: 'cat 1',
  p_1_name: 'name 1',
  p_2_category: 'cat 2',
  p_2_name: 'name 2',
  p_2_contact: '1234567',
  k_id: 111,
  k_name: 'abc'
}

var o ={};
Object.keys(data).forEach(key => {
  var splited =  key.split(/_(.+)/);
  if (!o[splited[0]]) {
    o[splited[0]] = {};
  }
  var splited1 = splited[1].split(/_(.+)/);
  if (splited1.length < 3) {
    o[splited[0]][splited[1]] = data[key];
  } else {
    if (!o[splited[0]][splited1[0]]){ 
      o[splited[0]][splited1[0]] = {};
    }
    o[splited[0]][splited1[0]][splited1[1]] = data[key];
  }
});
console.log(o);


Cả mã của bạn và mã trong câu trả lời từ Nenad đều tạo ra một cái gì đó hơi khác so với đầu ra mẫu của bạn. ( mlà một đối tượng đơn giản, không phải là một mảng các đối tượng thuộc tính đơn.) Những kết quả này có vẻ hợp lý hơn nhiều so với đầu ra mẫu của bạn. Đó có phải là những gì bạn thực sự muốn?
Scott Sauyet

@ScottSauyet ops, tôi đã đưa ra kết quả sai, tôi đã cập nhật nó, nhưng đoạn trích của tôi giống như những gì anh ấy đã làm. vâng, đó là những gì tôi muốn .. Cảm ơn
Devb

Câu trả lời:


6

Bạn có thể sử dụng reducephương thức sẽ tạo cấu trúc lồng nhau tương tự chỉ với các đối tượng.

var data = {
  m_name: 'my name',
  m_address: 'my address',
  p_1_category: 'cat 1',
  p_1_name: 'name 1',
  p_2_category: 'cat 2',
  p_2_name: 'name 2',
  p_2_contact: '1234567',
  k_id: 111,
  k_name: 'abc'
}


const result = Object
  .entries(data)
  .reduce((a, [k, v]) => {
    k.split('_').reduce((r, e, i, arr) => {
      return r[e] || (r[e] = arr[i + 1] ? {} : v)
    }, a)

    return a
  }, {})

console.log(result)


2

Tôi không biết định dạng đầu ra đó là thứ bạn thực sự tìm kiếm hay đơn giản là thứ tốt nhất bạn có thể đạt được. Một cách khác là tạo ra thứ gì đó như thế này:

{
  m: {name: "my name", address: "my address"},
  p: [
    {category: "cat 1", name: "name 1"},
    {category: "cat 2", name: "name 2"}
  ]
}

Có một sự khác biệt lớn giữa điều này và đầu ra mã của bạn. pbây giờ là một mảng đơn giản của các đối tượng chứ không phải là một đối tượng 1- và 2-exexed. Rất có thể điều này không hữu ích với bạn, nhưng nó là một sự thay thế thú vị. Ngoài ra còn có một sự khác biệt thứ hai so với đầu ra mẫu bạn cung cấp. Cả mã gốc của bạn và câu trả lời từ Nenad trở lại m: {name: "my name", address: "my address"}thay vì yêu cầu m: [{name: "my name"}, {address: "my address"}]. Điều này có vẻ hợp lý hơn nhiều đối với tôi và tôi cũng đã thực hiện theo cách này.

Đây là một số mã sẽ làm điều này:

// Utility functions

const isInt = Number.isInteger

const path = (ps = [], obj = {}) =>
  ps .reduce ((o, p) => (o || {}) [p], obj)

const assoc = (prop, val, obj) => 
  isInt (prop) && Array .isArray (obj)
    ? [... obj .slice (0, prop), val, ...obj .slice (prop + 1)]
    : {...obj, [prop]: val}

const assocPath = ([p = undefined, ...ps], val, obj) => 
  p == undefined
    ? obj
    : ps.length == 0
      ? assoc(p, val, obj)
      : assoc(p, assocPath(ps, val, obj[p] || (obj[p] = isInt(ps[0]) ? [] : {})), obj)

// Main function

const hydrate = (flat) => 
  Object .entries (flat) 
    .map (([k, v]) => [k .split ('_'), v])
    .map (([k, v]) => [k .map (p => isNaN (p) ? p : p - 1), v])
    .reduce ((a, [k, v]) => assocPath (k, v, a), {})

// Test data

const data = {m_name: 'my name', m_address: 'my address', p_1_category: 'cat 1', p_1_name: 'name 1', p_2_category: 'cat 2', p_2_name: 'name 2' }

// Demo

console .log (
  hydrate (data)
)
.as-console-wrapper {min-height: 100% !important; top: 0}

Mã này được lấy cảm hứng từ Ramda (trong đó tôi là tác giả). Các chức năng tiện ích path, assocassocPathcó các API tương tự như Ramda, nhưng đây là những hiện thực duy nhất (mượn từ câu trả lời khác .) Kể từ khi chúng được chế tạo vào Ramda, chỉ có hydratechức năng sẽ là cần thiết nếu dự án của bạn sử dụng Ramda.

Sự khác biệt lớn giữa câu trả lời này và câu trả lời (xuất sắc!) Của Nenad là việc hydrat hóa đối tượng của chúng ta có tính đến sự khác biệt giữa các khóa chuỗi, được giả sử là dành cho các đối tượng đơn giản và các số được coi là dành cho mảng. Tuy nhiên, vì chúng được tách ra khỏi chuỗi ban đầu của chúng tôi ( p_1_category), điều này có thể dẫn đến sự mơ hồ nếu đôi khi bạn có thể muốn chúng là đối tượng.

Tôi cũng sử dụng một thủ thuật mà là một chút xấu xí và có lẽ sẽ không mở rộng để các giá trị số khác: Tôi trừ 1 từ số sao cho 1trong p_1_categorybản đồ để chỉ số 0. Nếu dữ liệu đầu vào của bạn trông giống như p_0_category ... p_1_categorythay vì p_1_category ... p_2_categorychúng tôi có thể bỏ qua điều này.

Trong mọi trường hợp, có một cơ hội thực sự rằng điều này trái với yêu cầu cơ bản của bạn, nhưng nó có thể hữu ích cho những người khác.


1
Tôi thích nhìn thấy các phương pháp khác nhau ở đây. Có lẽ pathcó thể ps.reduce((o = {}, p) => o[p], obj)chỉ là ps.reduce(prop, obj)?
Cảm ơn bạn

Xin lỗi, tôi đã cung cấp thông tin sai. Một cái đúng nên dựa trên đoạn trích của tôi. Nhân tiện, điều đó thật tuyệt. nó có thể hữu ích trong tương lai. Cảm ơn.
Devb

1
@ Cảm ơn: Mặc dù phiên bản đó pathcó thể hoạt động cho trường hợp cụ thể này, nhưng nó lại mất đi tính an toàn vô hiệu của phiên bản trên. Đồng ý về sự đa dạng của các câu trả lời; có vẻ như những câu hỏi thú vị có thể mang lại nhiều lựa chọn thay thế thú vị.
Scott Sauyet

2

không cần phân loại

Đầu ra đề xuất trong bài viết của bạn không theo một mẫu. Một số mục nhóm thành mảng trong khi các mục khác nhóm thành đối tượng. Vì các đối tượng giống như mảng hoạt động giống như mảng , chúng ta sẽ chỉ sử dụng các đối tượng.

Đầu ra trong câu trả lời này giống như của Nenad nhưng chương trình này không yêu cầu các khóa của đối tượng được sắp xếp trước -

const nest = (keys = [], value) =>
  keys.reduceRight((r, k) => ({ [k]: r }), value)

const result =
  Object
    .entries(data)
    .map(([ k, v ]) => nest(k.split("_"),  v))
    .reduce(merge, {})

console.log(result)

Đầu ra -

{
  m: {
    name: "my name",
    address: "my address"
  },
  p: {
    1: {
      category: "cat 1",
      name: "name 1"
    },
    2: {
      category: "cat 2",
      name: "name 2",
      contact: "1234567"
    }
  },
  k: {
    id: 111,
    name: "abc"
  }
}

hợp nhất

Tôi đang mượn một cái chung mergemà tôi đã viết trong một câu trả lời khác. Những lợi ích của việc sử dụng lại các chức năng chung là rất nhiều và tôi sẽ không nhắc lại chúng ở đây. Đọc bài viết gốc nếu bạn muốn biết thêm -

const isObject = x =>
  Object (x) === x

const mut = (o = {}, [ k, v ]) =>
  (o[k] = v, o)

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .map
      ( ([ k, v ]) =>
          isObject(v) && isObject(left[k])
            ? [ k, merge (left[k], v) ]
            : [ k, v ]
      )
    .reduce(mut, left)

Hợp nhất nông làm việc như mong đợi -

const x =
  [ 1, 2, 3, 4, 5 ]

const y =
  [  ,  ,  ,  ,  , 6 ]

const z =
  [ 0, 0, 0 ]

console.log(merge(x, y))
// [ 1, 2, 3, 4, 5, 6 ]

console.log(merge(y, z))
// [ 0, 0, 0, <2 empty items>, 6 ]

console.log(merge(x, z))
// [ 0, 0, 0, 4, 5, 6 ]

Và hợp nhất sâu sắc quá -

const x =
  { a: [ { b: 1 }, { c: 1 } ] }

const y =
  { a: [ { d: 2 }, { c: 2 }, { e: 2 } ] }

console.log(merge (x, y))
// { a: [ { b: 1, d: 2 }, { c: 2 }, { e: 2 } ] }

Mở rộng đoạn trích bên dưới để xem kết quả của chúng tôi trong trình duyệt của riêng bạn -


0

Sử dụng forEachvòng lặp trên đối tượng.
Tách khóa dựa trên seperator và di chuyển ngang trên mảng
Cho đến khóa cuối cùng, tạo đối tượng trống và duy trì đối tượng hiện tại trong con trỏ / nhân vật.
Trên khóa cuối cùng, chỉ cần thêm giá trị.

const unflatten = (data, sep = "_") => {
  const result = {};
  Object.entries(data).forEach(([keys_str, value]) => {
    let runner = result;
    keys_str.split(sep).forEach((key, i, arr) => {
      if (i === arr.length - 1) {
        runner[key] = value;
      } else if (!runner[key]) {
        runner[key] = {};
      }
      runner = runner[key];
    });
  });
  return result;
};

const data ={
  m_name: 'my name',
  m_address: 'my address',
  p_1_category: 'cat 1',
  p_1_name: 'name 1',
  p_2_category: 'cat 2',
  p_2_name: 'name 2',
  p_2_contact: '1234567',
  k_id: 111,
  k_name: 'abc'
}

console.log(unflatten(data));

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.