Before we joke about the title, let’s talk about some unusual json.stringify operations.

Look at an example

const data = {
    global: {
        preview: 'https://bucket.oss-cn-hangzhou.aliyuncs.com/images/p1.png'
    },
    layouts: [{elements: [{imageUrl: 'https://bucket.oss-cn-hangzhou.aliyuncs.com/images/p2.png'.elements: [{imageUrl: 'https://bucket.oss-cn-hangzhou.aliyuncs.com/images/p3.png'}]}, {url: 'https://bucket2.oss-cn-hangzhou.aliyuncs.com/images/p2.png'}].backgroundImage: 'https://bucket.oss-cn-hangzhou.aliyuncs.com/images/p4.png'}};Copy the code

You take the template data from the interface as usual and display the image on the page, Oss-cn-hangzhou.aliyuncs.com to expedite the access of image resources. Change the domain name bucket.oss-cn-hangzhou.aliyuncs.com to CDN some.oss-cdn.com for all the image resources in the data.

Knowing the data structure, we can of course just walk through the entire object:

function replace(url) {
    return url.replace('bucket.oss-cn-hangzhou.aliyuncs.com'.'some.oss-cdn.com');
}
Copy the code
function replaceOssHost(data) {
    data.global.preview = replace(data.global.preview);
    data.layouts.forEach(layout= > {
        layout.backgroundImage = replace(layout.backgroundImage);
        layout.elements.forEach(element= > {
            element.url = replace(element.url);
            element.imageUrl = replace(element.imageUrl);
            if (element.elements) {
                // ...}}); });return data;
}
Copy the code

Well, it looks very unreliable! Add any new image resource field and the traversal rules will change, so we need an alternative method that is independent of the specific deconstruction, something like this:

function replaceOssHost(data) {
    Object.entries(data).forEach(([k, v]) = > {
        if (v && typeof v === 'object') {
            return replaceOssHost(v);
        }
        if (typeof v === 'string') { data[k] = replace(v); }});return data;
}
Copy the code

Isn’t that just recursively going through an object? So what does it have to do with json.stringify?

The json.stringify argument is often ignored

Question: Do you know the second argument to the json.stringify and json.parse methods?

If you are not sure, you may wish to take a look at the MDN document:

  • MDN JSON.parse

  • MDN JSON.stringify

JSON.stringify(value[, replacer [, space]])

Replacer If the parameter is a function, each attribute of the serialized value is converted and processed by the function during serialization; If the parameter is an array, only the property names contained in the array will be serialized into the final JSON string; If this parameter is null or not provided, all attributes of the object are serialized.

JSON.parse(text[, reviver])

Reviver convertor, which, if passed, can be used to modify the original value generated by parsing before the parse function returns.

Parse and json. stringify both provide a second optional argument:

  • cankey valueTo iterate over all traversable properties of the current object.
  • Support for returning a new value to replace the value of the current attribute.

Json. parse and json. stringify traverse in a slightly different order:

const data = {
    "1": 1."2": 2."3": {
        "4": 4."5": {
            "6": 6}}};const dataStr = JSON.stringify(data);

JSON.stringify(data, function (k, v) {
    console.log(`${k}: `, v);
    return v;
});
// Output: the root object k is empty, traversal from outer layer to inner layer
/ / : {' 1 ', 1, '2', 2, '3' : {' 4 ', 4 '5' : {' 6 ', 6}}} object itself as the key will be empty
// 1: 1
// 2: 2
// 3: {'4': 4, '5': {'6': 6}}
// 4: 4
// 5: {'6': 6}
// 6: 6

JSON.parse(dataStr, function (k, v) {
    console.log(`${k}: `, v);
    return v;
});
// Output: the root object k is empty, starting at the innermost attribute level and working up to the top level, which is the parse value itself
// 1: 1
// 2: 2
// 4: 4
// 6: 6
// 5: {'6': 6}
// 3: {'4': 4, '5': {'6': 6}}
/ / : {' 1 ', 1, '2', 2, '3' : {' 4 ', 4 '5' : {' 6 ', 6}}} object itself as the key will be empty
Copy the code

Json. stringify’s traversal is a little more intuitive. Json. parse takes precedence over the child of the object when traversing an object. Most of the time we don’t care much about traversal order, but note that the key is empty when traversing the object itself.

Going back to the original example, using json.stringify can be very convenient to implement host substitution:

Use json.stringify for object traversal

The substitution can be done in a few simple lines:

function replaceOssHost(data) {
    return JSON.parse(JSON.stringify(data, function (k, v) {
        if (v && typeof v === 'string') {
            return replace(v);
        }
        return v;
    }));
}
Copy the code

Of course, this is a simple example, but let’s look at a slightly more complicated one:

const data = {
    global: {
        preview: 'data:image/png; base64,iVBORw0KGgoAAAANSUhE... '
    },
    layouts: [{elements: [{imageUrl: 'data:image/png; base64,iVBORw0KGgoAAAANSUhE... '.elements: [{imageUrl: 'data:image/png; base64,iVBORw0KGgoAAAANSUhE... '}]}, {url: 'data:image/png; base64,iVBORw0KGgoAAAANSUhE... '}].backgroundImage: 'data:image/png; base64,iVBORw0KGgoAAAANSUhE... '}};Copy the code

The same image resource, but the resource became Base64 Data, and it is now required to upload the Base64 resource to OSS and replace it with the corresponding link when saving template data data.

Base64 uploads are asynchronous, and json. stringify’s replacer is a synchronous function. We cannot upload and replace images directly in the replacer.

Implementation is actually very simple, will upload and replace the separation of good, directly on the code:

const data = {
    global: {
        preview: 'data:image/png; base64,iVBORw0KGgoAAAANSUhE... '}};// Traversal returns a resource map
function getResources(data) {
    const resources = new Map(a);const content = JSON.stringify(data, function (_, v) {
        if (v && typeof v === 'string' && v.includes('base64,')) {
            resources.set(v, v);
        }
        return v;
    });
    return { content, resources };
}

// base64 uploads resources
function uploadBase64(base64) {
    return new Promise((resolve) = > {
        setTimeout(() = > {
            resolve('https://some.oss-cdn.com/images/xxx.png');
        }, 200);
    });
}

async function uploadResources(data) {
    const { content, resources } = getResources(data);
    // Upload base64 to replace url
    for (let [_, value] of resources) {
        const url = await uploadBase64(value);
        resources.set(value, url);
    }
    // Replace Base64 with url based on resources Value and URL mapping
    return JSON.parse(content, function(_, v) {
        if(! v ||typeofv ! = ='string'| |! v.includes('base64,')) {
            return v;
        }
        return resources.get(v) || v;
    });
}
Copy the code

The details of json.stringify and json.parse parameters have been little discussed before, but they can be very useful in certain business scenarios. Try json.stringify and json.parse the next time you encounter recursive data manipulation.

over~!