Transform flattened JSON data meant for forms back into its original object structure

My task requires me to convert specific form fields that contain "object nesting" in the field names back to the objects themselves. Here are some examples of typical form field names:

  • phones_0_patientPhoneTypeId
  • phones_0_phone
  • phones_1_patientPhoneTypeId
  • phones_1_phone

The form fields above were extracted from an object similar to the one shown below (refer to "Data"). The format of the object needs to be reconstructed. Any form field with an underscore _ character in its name should undergo this conversion. Additionally, if the segment between underscores in the form field name is numeric, it represents a JavaScript array; otherwise, it signifies an object.

I managed to come up with a basic implementation for flattening the original object for form use, but I am facing challenges with reversing the process. Below the object/data section, I have included my current attempt at achieving this. One issue with my implementation is that it does not handle array indexes correctly, which could be problematic when encoding the object as JSON, especially considering sparse arrays. For example, even if "phones_1" exists and "phones_0" does not, I still want to ensure that a slot exists for phones[0] even if the value is null.

Data:

var obj = {
  phones: [{
      "patientPhoneTypeId": 4,
      "phone": "8005551212"
    },
    {
      "patientPhoneTypeId": 2,
      "phone": "8885551212"
    }
  ]
};

Current Code:

var unflattened = {};

for (var prop in values) {
  if (prop.indexOf('_') > -1) {
    var lastUnderbarPos = prop.lastIndexOf('_');

    var nestedProp = prop.substr(lastUnderbarPos + 1);
    var nesting = prop.substr(0, lastUnderbarPos).split("_");

    var nestedRef, isArray, isObject;

    for (var i = 0, n = nesting.length; i < n; i++) {
      if (i === 0) {
        nestedRef = unflattened;
      }

      if (i < (n - 1)) { // not last
        if (/^\d+$/.test(nesting[i + 1])) {
          isArray = true;
          isObject = false;
        } else {
          isArray = true;
          isObject = false;
        }

        var currProp = nesting[i];

        if (!nestedRef[currProp]) {
          if (isArray) {
            nestedRef[currProp] = [];
          } else if (isObject) {
            nestedRef[currProp] = {};
          }
        }

        nestedRef = nestedRef[currProp];
      } else {
        nestedRef[nestedProp] = values[prop];
      }
    }
  }

Answer №1

To simplify the understanding of array and object structures in form field names, I recommend using this naming convention:

property.<remaining>     => refers to the "property" object
property[i].<remaining>  => points to the "property" array with index "i"

With this approach, recreating the array becomes more straightforward without complex string and index manipulations. Assuming you start with an empty root object, you can implement a method named set that takes the property string as is and constructs the object regardless of its nested depth.

var object = {};

object.set = function(key, value) {
    var parts = key.split(".");
    var current = this; // starting at root
    var arrayRegex = /(\w+)\[(\d+)\]/, nameAndIndex, name, index;

    while(parts.length) {
        part = parts.shift();
        nameAndIndex = part.match(arrayRegex);
        if(nameAndIndex != null) { // if property is an array
            name = nameAndIndex[1];
            index = nameAndIndex[2];
            current[name] = current[name] || [];
            current[name][index] = current[name][index] || {};
            current = current[name][index];
        } else { // if property is an object
            current[part] = current[part] || {};
            current = current[part]; 
        }
    }

    current[part] = value;
};

This setup can handle deeply nested structures but requires an object at each level. For instance, it supports

someProperty[1].anotherProperty[2]
, not someProperty[1][2]. Also, the last item must be an object property, not an array element (e.g., x.y.z.anArray[1].lastProperty over x.y.z.lastProperty[1]). Adjustments may be needed in the set method based on specific use cases.

I've created a demo on http://jsfiddle.net/ysQpF/ showcasing these test scenarios:

object.set('phones[0].phoneType', 1);
object.set('phones[0].phone', '312-223-4929');
object.set('phones[0].details[0].areaCode', '312');
object.set('phones[0].details[0].region[0].name', 'Alaska');
object.set('phones[0].details[0].region[1].name', 'California');
object.set('phones[1].phoneType', 2);
object.set('phones[1].phone', '408-332-3011');

Answer №2

When dealing with each form field:

Example: phones_0_patientPhoneTypeId

  1. Begin by splitting the name using _ to create an array:
    ["phones","0","patientPhoneTypeId"]
  2. Reverse that array:
    ["patientPhoneTypeId","0","phones"]
  3. Assign a template object to a variable, for instance o
  4. Continue to use pop() on the array until it is empty, and for every element except the last one do o = o[resultOfPop]
  5. For the last element, simply assign the value like so: o[resultOfPop] = valueFromField

If you choose not to utilize a template object, you will need to include an empty object or array in step 4.

Answer №3

Thank you for the valuable input from others. I made some adjustments to my existing code and finally got it working, I was on the right track.

var transformedObject = {};

for (var key in data) {
    if (key.includes('_')) {
        var path = key.split("_");
        var currentKey = path.pop();

        if (!path.length || !currentKey) {
            console.log("Unable to convert form field name: '" + originalKey + "' to a 'nested' object");
            continue;
        }

        var reference, tempKey, isArr, isObj;

        for (var j = 0, length = path.length; j < length; j++) {
            if (j === length - 1) { 
                isArr = /^\d+$/.test(currentKey);
            } else {
                isArr = /^\d+$/.test(path[j + 1]);
            }
            
            isObj = !isArr;
            tempKey = path[j];

            if (j === 0) {
                reference = transformedObject;
            }

            if (!reference[tempKey]) {
                if (isArr) {
                    reference[tempKey] = [];
                } else {
                    reference[tempKey] = {};
                }
            }

            reference = reference[tempKey];

            if (j === length - 1) { 
                reference[currentKey] = data[key];
            }
        }
    }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

A Guide on Retrieving the Initial and Final Values in a JSON Object

Is there a way to extract only the first and last element from a JSON using Gson? I want the data to be displayed in this specific format: System.out.println("Student: BobGoblin - result: 59"); I attempted the following code, but it is returning the enti ...

The Ajax login validation is not displaying

Currently, I am utilizing Laravel 4 to implement login validation through the use of AJAX. Below is the JavaScript validation code that I have: jQuery('#form-signin').submit(function() { var url = $(this).attr("action"); jQuery.ajax({ ...

Tracking pixels for website analytics

Have you ever thought about why web analytics software opts to use a 1x1 gif element to send a beacon instead of just including a <script src='http://my.analytics.com/script.js?my=params'></script> tag for sending the beacon? This w ...

Retrieving Parameter Values in JAX-RS Request Bodies

A task in my javascript involves making an ajax post request to one of my JAX-RS functions. Here is a snippet of the code: var postPackingListRequest = $http({ method: "post", url: "/rest/v1/submit", data: $scope ...

Disabling Express BodyParser for handling file uploads in Node.js

When developing my web application with Node.js + Express, I've found the connect BodyParser to be incredibly useful. However, I'm looking for a way to have more control over multipart form-data POSTS so that I can stream the input directly to an ...

Manipulate target.href using jQuery when referencing an image within an anchor tag

My AJAX method handles all links in the HTML by using the preventDefault function and then loading the loadPage function. It's effective on all links except for those containing an <img> element. When an <a> tag has an <img> element ...

Fetching an image from a URL using Node.js

My problem lies in downloading images with unknown file extensions (such as jpg, png, jpeg, or bmp) from a URL. I want to check the Content-Length of the image and if it is greater than 0, download it to a file. If not, I want to try downloading the image ...

Exploring JQuery Autocomplete and Optimizing JSON Parsing

I have experience in programming, but I'm new to working with Javascript and JQuery. I'm struggling to determine where to parse the JSON results I receive after making an AJAX query for JQuery's autocomplete feature. Here is the code I hav ...

Retrieve only the most recent input value

Currently, I am working with a text input to capture user input. As of now, here is my code snippet: $(myinput).on('input', function() { $(somediv).append($(this).val()); }); While I want to continue using the append function, I am facing an i ...

Cannot utilize structuredClone() on the value of the reference variable

I am looking to implement the structuredClone() function in my Vue application. I want to use it for creating a deep clone without resorting to methods like stringify and parse or third-party libraries. In my setup function, the following code works fine: ...

Retrieving values from multiple nested JSON arrays while developing a Flutter application

Is there a way to retrieve the JSON data in Flutter and present it in a hierarchical structure similar to the one depicted in the image? Specifically, I am looking to first show the parent menu name followed by the child menu name, then the child sub-menu ...

Arranging data based on a specific field name provided for sorting purposes

I recently started learning JavaScript and I'm working on creating a versatile sorting method that can be applied to different objects with varying fields. Currently, my implementation is not efficient because the objects I need to sort have different ...

Discover and transform any strings that begin with http & https into clickable links

Can I use jQuery to search a paragraph for all strings that begin with http & https and turn them into clickable links? I have my Twitter feed displayed on my website, but any lines starting with http & https are shown as regular text. Is it feasible to t ...

"Utilizing JQuery to enhance task management by implementing a delete function

I need help figuring out how to create a function that can delete tasks from my todo list. Currently, each task has its own remove button, but I want to implement a single remove button at the bottom that removes checked or crossed out tasks. Any suggestio ...

What is the proper way to initialize MongoDB and Express?

I have a unique app that utilizes express and interacts with mongodb. Here is how I typically kickstart my app: 1. Launch Mongodb mongod --dbpath data --config mongo.conf" 2. Initiate Express node server.js My pondering is, can these processes be comb ...

The text remains static and does not adjust its size when the screen is

I'm currently utilizing the text-only carousel feature found at this jsFiddle link. Below is the code I am using for resizing: window.addEventListener("resize", resetCarousel); function resetCarousel(){ setCarouselHeight('#carousel-text&apos ...

Leverage the basename feature in React Router when rendering on the server side

My website is being hosted from the directory /clientpanel on my server, making the URL http://xxx.yy/clientpanel. The client-side code looks like this: const history = useRouterHistory(createHistory)({ basename: "/clientpanel" }); render( <Ro ...

Is there a way to remove the initial word from a sentence?

Tue 26-Jul-2011 Looking to remove the initial word "Mon" using javascript with jQuery. Any suggestions on accomplishing this task? ...

When using Vue computed, an error is thrown stating "Issue in rendering: 'InternalError: too much recursion'" if the same key is referenced in computed

As a newcomer to Vue, I wanted to track how many times a function in the computed section gets called. To achieve this, I created the following component: const ComputedCounter = { name: "ComputedCounter", template: ` <span>{{ value } ...

Customizing Checkbox using CSS (additional assistance required)

I am currently working on implementing Checkbox Four with a custom design. You can check out the example I found on this website: My goal is to enhance this checkbox by incorporating red colored text 'N' when it's unchecked, and blue colore ...