Output a server generated json object in jade with

2020-07-06 06:09发布

问题:

I have a JSON object that does not conform to JSON standards, and I cannot change the structure of the object to make it adhere to JSON standards.

I need to make this object render in the middle of a javascript block in a Jade template. The object is actually a configuration object that is going in a function block in the template.

Here is the object.

{
  services: [],
  version: "1438276796258",
  country: "default",
  role: "User",
  Zack_Init: function () {

  },
  Zack_Global: function (event) {

  },
  Zack_PostRender: function () {

  }, 
  renderers: ['Renderer', 'NONE']
}

UPDATE Here is how I am getting that object from a JS file.

function readJSFile(url, filename, callback) {
  fs.readFile(url, "utf-8", function (err, data) {
    if (err) {
      callback(err);
      return;
    }
    try {
      callback(filename, data);
    } catch (exception) {
      callback(exception);
    }
  });
}

When JSON.stringify processes the object it drops the three functions in the conversion process.

I am adding a plunker to show the progress of the current solution. Which outputs the below. The only thing left is to remove the formatting characters.

{"services":[],"version":"1438276796258","country":"default","role":"User","Zack_Init":function () {\n\n },"Zack_Global":function (event) {\n\n },"Zack_PostRender":function () {\n\n },"renderers":["Renderer","NONE"]}

function convertToString(obj) {
  return JSON.stringify(obj, function(k, v) {
    return (typeof v === 'function' ? ['@@beginFunction@@', v.toString(), '@@endFunction@@'].join('') : v);
  }).replace(/"@@beginFunction@@|@@endFunction@@"/g, '');
}

obj = {
  services: [],
  version: "1438276796258",
  country: "default",
  role: "User",
  Zack_Init: function() {

  },
  Zack_Global: function(event) {

  },
  Zack_PostRender: function() {

  },
  renderers: ['Renderer', 'NONE']
};

$('#test').text(convertToString(obj));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="test"></div>

回答1:

In order to remove new line characters safely, 'comments' should be removed first:

function convertToString(obj) {
  return JSON.stringify(obj, function(k, v) {
    return (
      typeof v !== 'function' ? 
        v :
        v.toString().replace(/\/\/.*?$/mg, '')        // removes single line comments
                    .replace(/\/\*[\s\S]*?\*\//g, '') // removes multi-line comments
                    .replace(/[\r\n]/g, '')           // removes new line
    );
  }, 2).replace(/"(function.+)",?$/gm, '$1');         // removes quotes around functions
}

obj = {
  services: [],
  version: "1438276796258",
  country: "default",
  role: "User",
  Zack_Init: function() {
    // comment
    var a = 1;// comment //
    if(a === /*comment */ 3) {//comment
      /* comment
      comment*/
    }
    /*end*/
  },
  Zack_Global: function(event) {
  },
  Zack_PostRender: function() {
  },
  renderers: ['Renderer', 'NONE']
};

$('#result').text(convertToString(obj));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<pre id="result"></pre>



回答2:

If I'm understanding your request correctly (that you want to take the contents of a file that's loaded externally and then load it in a <script> block), I think the solutions that have been explored so far are largely over-engineered.

Here's what I have:

// index.js
var express = require('express');
var router = express.Router();
var fs = require('fs');

router.get('/', function(req, res, next) {
  readJSFile('./whacky_obj.js', 'whacky_json_obj.js', render); // used .js because OP implies that as the file source in the extraction method
  function render(fname, obj) {
    res.render('index', { jsObj: obj });
  }
});

// OP's readJSFile method
function readJSFile(url, filename, callback) {
  fs.readFile(url, "utf-8", function (err, data) {
    if (err) {
      callback(err);
      return;
    }
    try {
      callback(filename, data);
    } catch (exception) {
      callback(exception);
    }
  });
}

module.exports = router;

And then in your Jade file:

block content
  h1= title
  script!= jsObj

You can see the output from the jsObj variable if you swap script for pre, but script will do what you're asking.



回答3:

EDIT: Is the configuration object something you need to select or modify dynamically? Could you use an include? If not, then unescaped buffer code (http://jade-lang.com/reference/code/) seems like the way to go with the string passed back by readFile.


Stringify solution:

EDIT: better solution than my original proposal:

function funcyStringify(obj) {
    var funcMap = {};
    var sections = JSON.stringify(obj, function(k, v) {
        if (typeof v === 'function') {
            funcMap[k] = v;
            return ['@@function@@', k, '@@function@@'].join('');
        }
        return v;
    }).split(/"?@@function@@"?/g);
    for (var i = 1; i < sections.length-1; i+=2) {
        sections[i] = funcMap[sections[i]];
    }
    return sections.join('');
}

It'll need a bit more work if you need to have the same property name in nested objects referring to different functions.


Originally wrote:

Not familiar enough with Jade to type this out in it, but with EJS, you could do something like this:

<script>
var configObject = {
<% for (var key in configObject) {
     if (configObject.hasOwnProperty(key)) { %>
       <%- key %>:
       <% if (typeof configObject[key] === 'function') { %>
           <%- configObject[key].toString() %>
       <% } else { %>
           <%- JSON.stringify(configObject[key]) %>
       <% } %>
<% } } %>
};
</script>

Gets more complicated if you have functions below the top level.