Transforming a massive JSON object into a Blob concerns directly converting it into an ArrayBuffer or Blob to prevent exceeding the maximum string length error

Situation:

Within my application, I am encountering the following code:

let blob = new Blob([JSON.stringify(json)], {type: "application/json"});

This code sometimes fails because the maximum string length allowed in Chrome is approximately 500MB, and the size of the json object can exceed this limit.

Inquiry:

I am seeking a method to directly convert my json variable (a POJO) into a Blob, potentially through a streaming process that converts it to an ArrayBuffer incrementally. Alternatively, I am open to any other approach that enables the conversion of a large json object into a Blob without encountering the 'maximum string length' issue.

Considerations:

  • The proposed solution must be functional in web browsers.
  • If recommending an existing library, it should not require the json object to be just an array, as handling such cases is relatively straightforward. The library should instead support arbitrarily nested JSON objects where a significant portion of data may reside deep within the structure rather than being evenly distributed across top-level keys.
  • I am not interested in solutions that necessitate a stream as input and yield a stream of string segments as output, like json-stream-stringify or streaming-json-stringify. My preference is for transforming an already-loaded POJO into a Blob containing the stringified JSON content.

Additional Information:

  • How to use JSONStream to stringify a large object - This reference shares similarities with my scenario but focuses on JSONStream, designed for Node.js rather than browser environments. Additionally, the provided solution appears to save data key-by-key rather than in a deeply nested manner. If there exists a way to achieve this functionality in a web browser, resulting in an ArrayBuffer storing the oversized JSON string for complex nested objects, that would be considered a suitable answer.
  • How to use streams to JSON stringify large nested objects in Node.js? - Similar to the previous link.

Answer №1

One way to bypass this restriction is by creating the Blob in chunks using strings.

const header = 24;
const bytes = new Uint8Array((512 * 1024 * 1024) - header);
const bigStr = new TextDecoder().decode(bytes);
const arr = [];
for (let i=0; i<5; i++) {
  arr.push(bigStr);
}
console.log(new Blob(arr).size); // 2.7GB

The Blob constructor can also take other Blobs as input in its blobParts parameter, allowing us to use a simple recursive stringifier that creates a list of Blob objects instead of joining values with a DOMString separator.

This results in something like

new Blob(["{", <Blob>, ":", <Blob>, "}"]);

By following this approach, we avoid the 500MiB limit.

I made a quick modification to this implementation, but it hasn't undergone thorough testing, so you may want to verify it yourself:

// Modified JSON stringify method
// Your code here...

Answer №2

After some collaboration with ChatGPT-4, I have come up with a potential solution that appears promising based on initial testing.

function jsonToBlob(json) {
  const textEncoder = new TextEncoder();
  const seen = new WeakSet();

  function processValue(value) {
    if(seen.has(value)) {
      throw new TypeError("Converting circular structure to JSON");
    }

    if(value && typeof value.toJSON === "function") {
      value = value.toJSON();
    }

    if(typeof value === 'object' && value !== null) {
      seen.add(value);

      const blobParts = [];
      const entries = Array.isArray(value) ? value : Object.entries(value);
      for(let i = 0; i < entries.length; i++) {
        if(Array.isArray(value)) {
          blobParts.push(processValue(entries[i]));
        } else {
          const [key, val] = entries[i];
          blobParts.push(textEncoder.encode(JSON.stringify(key) + ':'), processValue(val));
        }
        if(i !== entries.length - 1) blobParts.push(textEncoder.encode(','));
      }

      const startBracket = Array.isArray(value) ? '[' : '{';
      const endBracket = Array.isArray(value) ? ']' : '}';
      return new Blob([textEncoder.encode(startBracket), ...blobParts, textEncoder.encode(endBracket)]);
    } else if(typeof value === 'function' || typeof value === 'undefined') {
      return textEncoder.encode("null");
    } else {
      // For primitives we just convert it to string and encode
      return textEncoder.encode(JSON.stringify(value));
    }
  }

  return processValue(json);
}

✅ Test 1:

let blob = jsonToBlob([{hello:{foo:[1,2,3], a:1, bar:["a", 2, {$hi:[1,2,3, {a:3}]}]}}, 4, new Date(),, (()=>{})]);
console.log(JSON.parse(await blob.text()));

✅ Test 2:

let json = {};
for(let i = 0; i < 600000; i++) {
  json[Math.random()] = Math.random().toString().repeat(100);
}
let blob = jsonToBlob(json);
console.log(blob); // ~1 GB

I will provide updates if any issues arise during the production use of this solution.

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

Storing sound recordings with GridFS

I am currently facing an issue with the following code, it is only working partially and I require assistance in fixing it. The function saveAudioToGridFS() should return a file ID to its calling function. Despite verifying that the value to be returned ...

Guide on utilizing map function in JavaScript and Vue to generate a fresh array

I am working on implementing a map method in JavaScript and Vue to generate a new array of objects while only including specific items in each object. I have designed a user interface with 2 checkboxes corresponding to 2 distinct objects: <div v-for ...

What is the procedure for transforming a JSON response into an array?

In my JSON response, I would like the values to be listed instead of appearing as a comma-separated single line. Here is a snippet of my code: URL url = new URL(urlStr); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMeth ...

Retrieve the present time of an ongoing CSS3 animation

I've been trying to retrieve the current time of a running animation, but I haven't had any luck so far. I can easily grab the start time using: myelement.addEventListener('webkitAnimationStart', function (evt){ console.log(evt.elaps ...

What's the best way to incorporate mouseenter() and mouseleave() into several elements sharing the same class?

I have a vision for a fun game where players must navigate from one platform to another without falling off the edges. The game starts when you hover over the initial platform, and success is achieved by reaching the final platform. However, failure occurs ...

The Angular route functions flawlessly in the development environment, but encounters issues when deployed to

I have a project built with Angular 2, During development on localhost, everything runs smoothly. However, once I build a production version using (npm run build: prod) and navigate to the route on the server, I encounter a 404 error indicating that the r ...

Integrating a Json service into my ASP.Net Web Api results in an altered response format when viewing in Postman

Code snippet for configuring services in Program.cs without using Json service: builder.Services.AddControllers(options => { options.ReturnHttpNotAcceptable = true; }).AddXmlDataContractSerializerFormatters(); Sample Json response from Postman (Pre ...

Exploring TypeScript integration with Google Adsense featuring a personalized user interface

After following a tutorial on implementing Google AdSense in my Angular App, I successfully integrated it. Here's what I did: In the index.html file: <!-- Global site tag (gtag.js) - Google Analytics --> <script> (function(i,s,o,g,r,a,m ...

Please display the Bootstrap Modal first before continuing

Currently, I'm facing a challenge with my JS code as it seems to continue running before displaying my Bootstrap Modal. On this website, users are required to input information and upon pressing the Save button, a function called "passTimeToSpring()" ...

Attempting to eliminate any dates that have already occurred

I am faced with an array containing various dates in string format such as "2016-08-12". My goal is to eliminate any dates that have already passed by comparing them to today's date. I am using TypeScript for this task. Here is a snippet of my datoAr ...

How to generate nested arrays in JSON format using MySQL data

My goal is to generate JSON using PHP from data in two MySQL tables: - Categories (unique) - Subcategories or Rights (multiple within the same category) However, I'm struggling to display multiple subcategories under one category. Currently, a ne ...

Struggling to find a solution for changing the color of a select box when an option is chosen

Here's an example of the HTML I'm working with: <select onclick="colorchanger()"> <option name="white" value="0">--Select--</option> <option name="red" value="1">Work</option> <option name="green" value="2" ...

Understanding how to use the 'this' variable in Vue components is essential for efficiently modifying DOM elements. Let's dive into a clarification on the usage of 'this'

Within my vue component, the structure is as follows: export default{ name: '', data: function () { return { var1 :{}, var2 : {}, ... } }, created: function () { this.methodName1() }, methods: { me ...

Checking and contrasting dates within Javascript

Looking to compare two dates with different formats: a) Date1 - 01 Feb 2019 b) Date2 - 2/3/2017 It's important to account for invalid dates and ensure that Date1 is greater than Date2. function CompareAndValidateDates() { var Date1 ="01 Feb 20 ...

Design a dynamic dropdown feature that is triggered when the chip element is clicked within the TextField component

Currently, I am facing difficulties in implementing a customized dropdown feature that is not available as a built-in option in Material UI. All the components used in this project are from Material UI. Specifically, I have a TextField with a Chip for the ...

Should I use "npm install" or "sudo npm install -g"

When it comes to installing certain packages, sometimes running sudo npm install -g is necessary, while for others simply using npm install is enough. What causes this difference and why does it exist? Take the following examples: npm install -g grunt-c ...

Leveraging the power of jQuery/javascript in conjunction with Google Forms

Currently, I am attempting to utilize jQuery and JavaScript with an iframe that contains a Google form. The code snippet is displayed below: <body> <iframe id="myFormFrame" src="https://docs.google.com/forms/d/smfjkafj809890dfafhfdfd/viewform?emb ...

Include a "remember me" feature in the Stripe form

I am currently working on an exciting project using Angular 6. Within my website, I have decided to integrate the Stripe payment system. However, I would like to incorporate a unique and default "remember me" feature offered by Stripe. <div id="card-e ...

What is the most efficient way to clear the input field in Angularjs when the backspace or delete keys are pressed?

Is there a way to reset an input field with AngularJS when the backspace or delete keys are pressed? I've implemented this fantastic directive, and it's been working great, except for when the user uses the backspace or delete key to clear the f ...

The switch statement remains unchanged for varying variables

Here is some code that I am working with: updateTable(selectedIndex) { console.log("running updateTable function"); let level = ''; // if (selectedIndex == 1){ // this.setState({level: 'day'}) // th ...