Getting key/value pairs from a string that resembles an Object (like JSON) by utilizing regular expressions

Seeking help with grabbing key/value pairs from a string like {float: 'null', another: 'foo'}. The desired output groups are float null and another foo. My current regex pattern is

/\{(?<set>(?<key>\w*)\s*\:\s*(?<value>.*)?\s?)*\}/g
It correctly captures the keys, but struggles to extract individual values. I'm using named groups for clarity but need assistance in extracting each key/value pair when multiple pairs exist. Appreciate any guidance.

Currently experimenting with

/\{(?<set>(?<key>\w*)\s*\:\s*(?<value>.*)?\s?)*\}/g
but the output shows:

the group 'set': float: 'null', another: 'foo' (correct)

the group 'key': float (correct)

the group 'value': 'null', another: 'foo' (incorrect, seeking just null)

Interested in capturing all key/value pairs if possible


Edit for more clarity:

My specific challenge involves parsing Markdown for use with custom components in Svelte, focusing on gathering props from markdown syntax on an image. According to my research on adding attributes to an image, it should resemble:

![Alt Text]https://<fullurl>.jpg "This is hover text"){prop1: 'foo', prop2: 'bar', float: true}

The reason for using regex is to parse the markdown string, not adhering strictly to JSON format (" around the keys).

Answer №1

Give this lengthy JavaScript regex a try:

/(?<key>\w*)\s*:\s*(?<value>(?<quote>["'])(?:\\.|.)*?\k<quote>|(?<number>[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)|(?<constant>true|false|null))/g

In action (view in full page, if not it's not all visible):

const regexKeyValue = /(?<key>\w*)\s*:\s*(?<value>(?<quote>["'])(?:\\.|.)*?\k<quote>|(?<number>[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)|(?<constant>true|false|null))/g;

document.getElementById('search').addEventListener('click', function () {
  const input = document.getElementById('input').value;

  let match,
      i = 1,
      output = [];

  while ((match = regexKeyValue.exec(input)) !== null) {
    console.log(`Match n°${i} : ` + match[0]);
    console.log('match.groups =', match.groups);

    // If the value is starting with quotes, then unquoted it and
    // also replace all the escape sequences (ex: "\\n" should become "\n").
    let value = match.groups.value;
    // If it's double quotes, let's use JSON.parse() as it will handle everything.
    if (value.match(/^"/)) {
      value = JSON.parse(value);
    }
    // If it's simple quotes, we can't use JSON.parse() so we have to convert
    // it to a double-quoted string before.
    else if (value.match(/^'/)) {
      value = value
        // 1) Remove the simple quotes around.
        .replace(/^'|'$/g, '')
        // 2) Replace all \' by '.
        // We have to search for all backslashes to handle also an escaped backslash.
        .replace(/\\(.)/g, function (fullMatch, afterBackslash) {
          if (afterBackslash === "'") {
            return "'";
          } else {
            return fullMatch;
          }
        })
        // 3) Escape all double quotes (" becomes \").
        .replace(/"/g, '\\"');
      // 4) Now use JSON.parse();
      value = JSON.parse(`"${value}"`);
    }
    
    // If it's a number or a constant, then convert the string to this real JS value.
    if (typeof match.groups.number !== 'undefined' ||
        typeof match.groups.constant !== 'undefined') {
      value = JSON.parse(match.groups.value);
    }

    console.log('value =', value);
    
    output.push(
      `Match n°${i++} :\n` +
      `  Key   : ${match.groups.key}\n` +
      `  Value : ${value}\n`
    );
  }

  document.getElementById('output').innerText = output.join("\n");
  document.getElementById('label').classList.remove('hidden');
});
textarea {
  box-sizing: border-box;
  width: 100%;
}

pre {
  overflow-y: scroll;
}

.hidden {
  display: none;
}
<textarea id="input" rows="10">{
  float: 'null',
  another: "foo",
  age: 45,
  type: '"simple" \' quote',
  comment: "Hello,\nA backslash \\, a tab \t and a \"dummy\" word.\nOk?",
  important: true,
  weight: 69.7,
  negative: -2.5
}</textarea>

<button id="search">Search for key-value pairs</button>

<p id="label" class="hidden">Matches:</p>
<pre><code id="output"></code></pre>

The same regular expression, with comments, with the x flag that PCRE offers:

/
(?<key>\w*)        # The key.
\s*:\s*            # : with optional spaces around.
(?<value>          # The value.
  # A string value, single or double-quoted:
  (?<quote>["'])   # Capture the double or single quote.
    (?:\\.|.)*?    # Backslash followed by anything or any char, ungreedy.
  \k<quote>        # The double or single quote captured before.
|
  # Int and float numbers:
  (?<number>[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)
|
  # true, false and null (or other constants):
  (?<constant>true | false | null)
)
/gx

Or better, on regex101, you'll have the colours and the explanation on the right column: https://regex101.com/r/bBPvUd/2

Answer №2

While it has been pointed out in the discourse, the use of eval() is often labeled as "dangerous" or at least not secure. The exact reasons elude me currently, but it has something to do with vulnerabilities like cross-site scripting. That being said, if employed within a controlled and "secure" environment for processing input that is under your complete jurisdiction, there might still be some leeway.

const text=`Starting off with some content and now onto the image: 
![Alt Text]https://<url>.jpg "This is hover info"){name: 'apple', type: 'fruit', juicy: true} 
and more words.

Another paragraph followed by another image
![Alt Text2]https://<imageURL>.jpg "Alternate hover text"){name: 'banana', type: 'fruit', tropical: true} bringing us to the conclusion.`;

function riskyParse(input){
 return input.match(/\{[^}]+\}/g).map(segment=>eval(`(${segment})`));
}

// Produces an array of all image property objects:
console.log(riskyParse(text));

In addition to its inherent risks, the above approach is not foolproof since including the character "}" in property values can lead to complications...

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

Customize your Shopify Messenger icon using JavaScript!

Shopify recently introduced a new feature that allows customers to contact store owners through messenger. My client has requested me to customize the appearance of this icon with their branded icon instead. https://i.stack.imgur.com/uytpd.png I have not ...

Adding custom script bundles in NextJS is a great way to enhance the functionality and

I am facing a challenge with incorporating legacy custom JavaScript files into my project. These scripts need to be bundled and linked in the _document.js file with a hash included in the filename. What is the most effective approach to achieve this? I h ...

Creating a Pre-authentication service for AWS Cognito using VueJS

Implementation of Pre-Authentication feature is needed in my VueJS application for the following tasks: Validation of ID/Refresh Token to check if it has expired. If the IdToken has expired, the ability to re-generate it using the Refresh Token or altern ...

What's the optimal method for integrating bootstrap.css into a Nuxt project?

Here is a snippet from my nuxt.config.js file: head: { link: [ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, // load bootststrap.css from CDN //{ type: 'text/css', rel: &ap ...

Learn how to dynamically apply a class to a table row in a dataTable based on the presence of an ID within the data,

Currently, I am utilizing dataTables to retrieve the data on the front-end. To illustrate this, I will incorporate a mock datatable in this instance. My objective is to apply highlighting/addClass to the chosen row and store it in the sessionStorage so tha ...

Global registration of Vue components involves making them available application wide

I've developed a vue application using the vue-cli As I create components, I want to utilize them in this manner: <template> <oi-list> <oi-list-header>Task ID</oi-list-header> <oi-list-header>Tasks T ...

An error has occurred in the Javascript/EJS code: TypeError - Unable to access the 'name' property of null

Upon submitting my first post request, an error is thrown by the code: node:events:504 throw er; // Unhandled 'error' event ^ TypeError: Cannot read properties of null (reading 'name') Currently utilizing Node.js and mongoose Instal ...

What are the best practices for presenting Motion JPEG binary data streams using Angular, Ionic, and JavaScript?

I am developing an application for a device that will receive a POST request and send back a binary data stream in the format of multipart/x-mixed-replace. My goal is to display this stream on a specific section of my app's homepage. After conducting ...

Can you explain the distinction between a def interface and a dto interface within Angular development?

Currently, I am tasked with continuing a project that was initiated by someone else. Within the model folder, there are two interface files labeled def and dto. However, the distinction between these def and dto interface files is unclear to me. I would gr ...

Using the native functionality, submitting a razor form with JQuery AJAX in MVC6

Is there a specific method to submit a form using jQuery AJAX in MVC6 while still utilizing the Auto Binding functionality of ASP.NET MVC? In previous versions of MVC, one could use jquery.unobtrusive-ajax and simply utilize @using (Ajax.BeginForm("SaveDa ...

Is it possible to implement pagination for loading JSON data in chunks in jsGrid?

Currently, I am utilizing jsgrid and facing an issue with loading a JSON file containing 5000 registries into a grid page by page. My goal is to display only 50 registries per page without loading all 5000 at once. Even though I have implemented paging in ...

Terminal displaying a running error for a NestJS CLI project

Encountered an error while running the command yarn start:dev in my WebStorm integrated terminal. Error Snapshot https://i.sstatic.net/Oug8y.png Sharing my package.json file below { "name": "test-project", "version": "0.0.1", "description": "", ...

Observable not defined in facade pattern with RxJS

(After taking Gunnar's advice, I'm updating my question) @Gunnar.B Tool for integrating with API @Injectable({ providedIn: 'root' }) export class ConsolidatedAPI { constructor(private http: HttpClient) { } getInvestments(search?: ...

What is the best way to incorporate an array of elements into Firebase?

How can I store data from an array to Firebase in a simple manner? Let's say I have an array of elements. function Something() { var elements=new Array() elements[0]=10; elements[1]=20; elements[2]=30; database = firebase.databas ...

What are the differences between the detail_page and the list_page?

Is there a way to differentiate between In my Vue project, I am able to navigate from the list_page to its detail_page using this.$router.go(-1) to return back to the list_page. I can also access the list_page from other_pages, but how do I distinguish b ...

Label button for personalized file selection field

Looking to create a unique file selector button instead of the standard <input type='file'/>. The goal is to have a clickable button that performs the same action. <input style={{ display: 'none&ap ...

napi and SWIG: ThreadSafeFunction only runs in a thread after the thread has finished its execution

Check out this repository I created, where a thread-safe function in a SWIG C++ class is executed using node-addon-api. The thread within the SWIG C++ class is triggered using the napi BlockingCallback as shown below: // C++ thread implementation for (int ...

The Ocelot API Gateway is a powerful tool for managing

I encountered an issue while working on my API gateway project. I initially installed the latest version of Ocelot (16.0.1), but it did not function correctly. The problem was resolved by reverting back to Ocelot version 15.0.6, while keeping my .NET Core ...

Listen for incoming data from the client in the form of an ArrayBuffer

I have been utilizing the ws library within nodejs to develop a small cursor lobby where players can interact. I have managed to utilize the server to send ArrayBuffers with bit streams to the client and successfully decode them. However, I am encountering ...

Ways to set a single element as selected from a group of elements retrieved from the backend

I need to set one element as checked by default based on a condition within a loop of dynamically generated elements. .component.ts checked(sensorname){ for (var i=0;i<this.sensorsarray.length; i++ ){ if(this.sensorsarray[i].name == this.se ...