以特定样式输出javascript对象

时间:2019-01-10 14:23:42

标签: javascript

我正在尝试遍历对象中的每个键并将其打印出来,就像JSON.stringify将其输出为

看起来像

{
    "name": "james",
    "profile": {
        "location": "ireland",
        "address": {
            "city": "dublin"
         },
        "hobbies": {}
    }
}

到目前为止,它的输出为

"name": "James",
"profile": {
"location": "ireland",
  "address": {
"city": "dublin",
}

const output = (data, node) => {
  for (let key in data) {
    if (typeof data[key] === 'object' && Object.keys(data[key]).length > 0) {
      if (node === true) {
        console.log(`  "${key}": {`)

      } else {
        console.log(`"${key}": {`)
      }

      output(data[key], true)


    } else {
      console.log(`"${key}": "${Object.keys(data[key]).length > 0 ? data[key] : '{}' }",`);
    }
  }

  console.log(`}`)

}

const obj = {
  "name": "James",
  "profile": {
    "location": "ireland",
    "address": {
      "city": "dublin"
    },
    "hobbies": {}
  }
};

output(obj);

如何为对象内部的每个键正确输出选项卡?

2 个答案:

答案 0 :(得分:1)

它仍然需要工作,但是您需要在缩进级别上保持跟踪。简单的方法就是传递它,而不是生成选项卡的数量。

const output = (data, node, indent = 0) => {
  const tabs = Array(indent).fill('\t').join('')
  const tabsClose = indent ? Array(indent-1).fill('\t').join('') : ''
  for (let key in data) {
    if (typeof data[key] === 'object' && Object.keys(data[key]).length > 0) {
      if (node === true) {
        console.log(`${tabs}"${key}": {`)

      } else {
        console.log(`${tabs}"${key}": {`)
      }

      output(data[key], true, indent + 1)


    } else {
      console.log(`${tabs}"${key}": "${Object.keys(data[key]).length > 0 ? data[key] : '{}' }",`);
    }
  }

  console.log(`${tabsClose}}`)

}

const obj = {
  "name": "James",
  "profile": {
    "location": "ireland",
    "address": {
      "city": "dublin"
    },
    "hobbies": {}
  }
};

output(obj);

与您的代码有关的问题是您不考虑右括号,并且末尾有逗号。

答案 1 :(得分:1)

JSON.stringify是一个COMPLEX方法,需要考虑很多边缘情况。此解决方案可能位于棒球场,改编自BestieJS。签名是stringify(value, replacerFunction, space),就像原始的JSON.stringify一样。

let charIndexBuggy = has("bug-string-char-index");
const functionClass = "[object Function]",
  dateClass = "[object Date]",
  numberClass = "[object Number]",
  stringClass = "[object String]",
  arrayClass = "[object Array]",
  booleanClass = "[object Boolean]";

function stringify(source, filter, width) {
  var whitespace, callback, properties, className;
  // A set of types used to distinguish objects from primitives.
  var objectTypes = {
    "function": true,
    "object": true
  };

  if (objectTypes[typeof filter] && filter) {
    if ((className = Object.prototype.toString.call(filter)) == functionClass) {
      callback = filter;
    } else if (className == arrayClass) {
      // Convert the property names array into a makeshift set.
      properties = {};
      for (var index = 0, length = filter.length, value; index < length; value = filter[index++], ((className = Object.prototype.toString.call(value)), className == stringClass || className == numberClass) && (properties[value] = 1));
    }
  }
  if (width) {
    if ((className = Object.prototype.toString.call(width)) == numberClass) {
      // Convert the `width` to an integer and create a string containing
      // `width` number of space characters.
      if ((width -= width % 1) > 0) {
        for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " ");
      }
    } else if (className == stringClass) {
      whitespace = width.length <= 10 ? width : width.slice(0, 10);
    }
  }
  // Opera <= 7.54u2 discards the values associated with empty string keys
  // (`""`) only if they are used directly within an object member list
  // (e.g., `!("" in { "": 1})`).
  return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []);
};

// Internal: Recursively serializes an object. Implements the
// `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
function serialize(property, object, callback, properties, whitespace, indentation, stack) {
  let value, className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, result, isProperty;

  var functionClass = "[object Function]",
    dateClass = "[object Date]",
    numberClass = "[object Number]",
    stringClass = "[object String]",
    arrayClass = "[object Array]",
    booleanClass = "[object Boolean]";

  try {
    // Necessary for host object support.
    value = object[property];
  } catch (exception) { }
  if (typeof value == "object" && value) {
    className = Object.prototype.toString.call(value);
    if (className == dateClass && !isProperty.call(value, "toJSON")) {
      if (value > -1 / 0 && value < 1 / 0) {
        // Dates are serialized according to the `Date#toJSON` method
        // specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
        // for the ISO 8601 date time string format.
        if (getDay) {
          // Manually compute the year, month, date, hours, minutes,
          // seconds, and milliseconds if the `getUTC*` methods are
          // buggy. Adapted from @Yaffle's `date-shim` project.
          date = floor(value / 864e5);
          for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++);
          for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++);
          date = 1 + date - getDay(year, month);
          // The `time` value specifies the time within the day (see ES
          // 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
          // to compute `A modulo B`, as the `%` operator does not
          // correspond to the `modulo` operation for negative numbers.
          time = (value % 864e5 + 864e5) % 864e5;
          // The hours, minutes, seconds, and milliseconds are obtained by
          // decomposing the time within the day. See section 15.9.1.10.
          hours = floor(time / 36e5) % 24;
          minutes = floor(time / 6e4) % 60;
          seconds = floor(time / 1e3) % 60;
          milliseconds = time % 1e3;
        } else {
          year = value.getUTCFullYear();
          month = value.getUTCMonth();
          date = value.getUTCDate();
          hours = value.getUTCHours();
          minutes = value.getUTCMinutes();
          seconds = value.getUTCSeconds();
          milliseconds = value.getUTCMilliseconds();
        }
        // Serialize extended years correctly.
        value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
          "-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
          // Months, dates, hours, minutes, and seconds should have two
          // digits; milliseconds should have three.
          "T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) +
          // Milliseconds are optional in ES 5.0, but required in 5.1.
          "." + toPaddedString(3, milliseconds) + "Z";
      } else {
        value = null;
      }
    } else if (typeof value.toJSON == "function" && ((className != numberClass && className != stringClass && className != arrayClass) || isProperty.call(value, "toJSON"))) {
      // Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
      // `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
      // ignores all `toJSON` methods on these objects unless they are
      // defined directly on an instance.
      value = value.toJSON(property);
    }
  }
  if (callback) {
    // If a replacement function was provided, call it to obtain the value
    // for serialization.
    value = callback.call(object, property, value);
  }
  if (value === null) {
    return "null";
  }
  className = Object.prototype.toString.call(value);
  if (className == booleanClass) {
    // Booleans are represented literally.
    return "" + value;
  } else if (className == numberClass) {
    // JSON numbers must be finite. `Infinity` and `NaN` are serialized as
    // `"null"`.
    return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
  } else if (className == stringClass) {
    // Strings are double-quoted and escaped.
    return quote("" + value);
  }
  // Recursively serialize objects and arrays.
  if (typeof value == "object") {
    // Check for cyclic structures. This is a linear search; performance
    // is inversely proportional to the number of unique nested objects.
    for (length = stack.length; length--;) {
      if (stack[length] === value) {
        // Cyclic structures cannot be serialized by `JSON.stringify`.
        throw TypeError();
      }
    }
    // Add the object to the stack of traversed objects.
    stack.push(value);
    results = [];
    // Save the current indentation level and indent one additional level.
    prefix = indentation;
    indentation += whitespace;
    if (className == arrayClass) {
      // Recursively serialize array elements.
      for (index = 0, length = value.length; index < length; index++) {
        element = serialize(index, value, callback, properties, whitespace, indentation, stack);
        results.push(element === undefined ? "null" : element);
      }
      result = results.length ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]";
    } else {
      // Recursively serialize object members. Members are selected from
      // either a user-specified list of property names, or the object
      // itself.
      forEach(properties || value, function(property) {
        var element = serialize(property, value, callback, properties, whitespace, indentation, stack);
        if (element !== undefined) {
          // According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
          // is not the empty string, let `member` {quote(property) + ":"}
          // be the concatenation of `member` and the `space` character."
          // The "`space` character" refers to the literal space
          // character, not the `space` {width} argument provided to
          // `JSON.stringify`.
          results.push(quote(property) + ":" + (whitespace ? " " : "") + element);
        }
      });
      result = results.length ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}";
    }
    // Remove the object from the traversed object stack.
    stack.pop();
    return result;
  }
};

function quote(value) {
  var result = '"', index = 0, length = value.length, useCharIndex = !charIndexBuggy || length > 10;
  var symbols = useCharIndex && (charIndexBuggy ? value.split("") : value);
  for (; index < length; index++) {
    var charCode = value.charCodeAt(index);
    // If the character is a control character, append its Unicode or
    // shorthand escape sequence; otherwise, append the character as-is.
    switch (charCode) {
      case 8: case 9: case 10: case 12: case 13: case 34: case 92:
        result += Escapes[charCode];
        break;
      default:
        if (charCode < 32) {
          result += unicodePrefix + toPaddedString(2, charCode.toString(16));
          break;
        }
        result += useCharIndex ? symbols[index] : value.charAt(index);
    }
  }
  return result + '"';
};

function has(name) {
  if (has[name] !== undefined) {
    // Return cached feature test result.
    return has[name];
  }
  var isSupported;
  if (name == "bug-string-char-index") {
    // IE <= 7 doesn't support accessing string characters using square
    // bracket notation. IE 8 only supports this for primitives.
    isSupported = "a"[0] != "a";
  } else if (name == "json") {
    // Indicates whether both `JSON.stringify` and `JSON.parse` are
    // supported.
    isSupported = has("json-stringify") && has("json-parse");
  } else {
    var value, serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}';
    // Test `JSON.stringify`.
    if (name == "json-stringify") {
      var stringify = exports.stringify, stringifySupported = typeof stringify == "function" && isExtended;
      if (stringifySupported) {
        // A test function object with a custom `toJSON` method.
        (value = function() {
          return 1;
        }).toJSON = value;
        try {
          stringifySupported =
            // Firefox 3.1b1 and b2 serialize string, number, and boolean
            // primitives as object literals.
            stringify(0) === "0" &&
            // FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
            // literals.
            stringify(new Number()) === "0" &&
            stringify(new String()) == '""' &&
            // FF 3.1b1, 2 throw an error if the value is `null`, `undefinedined`, or
            // does not define a canonical JSON representation (this applies to
            // objects with `toJSON` properties as well, *unless* they are nested
            // within an object or array).
            stringify(Object.prototype.toString) === undefined &&
            // IE 8 serializes `undefinedined` as `"undefinedined"`. Safari <= 5.1.7 and
            // FF 3.1b3 pass this test.
            stringify(undefined) === undefined &&
            // Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
            // respectively, if the value is omitted entirely.
            stringify() === undefined &&
            // FF 3.1b1, 2 throw an error if the given value is not a number,
            // string, array, object, Boolean, or `null` literal. This applies to
            // objects with custom `toJSON` methods as well, unless they are nested
            // inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
            // methods entirely.
            stringify(value) === "1" &&
            stringify([value]) == "[1]" &&
            // Prototype <= 1.6.1 serializes `[undefinedined]` as `"[]"` instead of
            // `"[null]"`.
            stringify([undefined]) == "[null]" &&
            // YUI 3.0.0b1 fails to serialize `null` literals.
            stringify(null) == "null" &&
            // FF 3.1b1, 2 halts serialization if an array contains a function:
            // `[1, true, Object.prototype.toString, 1]` serializes as "[1,true,],". FF 3.1b3
            // elides non-JSON values from objects and arrays, unless they
            // define custom `toJSON` methods.
            stringify([undefined, Object.prototype.toString, null]) == "[null,null,null]" &&
            // Simple serialization test. FF 3.1b1 uses Unicode escape sequences
            // where character escape codes are expected (e.g., `\b` => `\u0008`).
            stringify({ "a": [value, true, false, null, "\x00\b\n\f\r\t"] }) == serialized &&
            // FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
            stringify(null, value) === "1" &&
            stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
            // JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
            // serialize extended years.
            stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' &&
            // The milliseconds are optional in ES 5, but required in 5.1.
            stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' &&
            // Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
            // four-digit years instead of six-digit years. Credits: @Yaffle.
            stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' &&
            // Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
            // values less than 1000. Credits: @Yaffle.
            stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
        } catch (exception) {
          stringifySupported = false;
        }
      }
      isSupported = stringifySupported;
    }
    // Test `JSON.parse`.
    if (name == "json-parse") {
      var parse = exports.parse;
      if (typeof parse == "function") {
        try {
          // FF 3.1b1, b2 will throw an exception if a bare literal is provided.
          // Conforming implementations should also coerce the initial argument to
          // a string prior to parsing.
          if (parse("0") === 0 && !parse(false)) {
            // Simple parsing test.
            value = parse(serialized);
            var parseSupported = value["a"].length == 5 && value["a"][0] === 1;
            if (parseSupported) {
              try {
                // Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
                parseSupported = !parse('"\t"');
              } catch (exception) { }
              if (parseSupported) {
                try {
                  // FF 4.0 and 4.0.1 allow leading `+` signs and leading
                  // decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow
                  // certain octal literals.
                  parseSupported = parse("01") !== 1;
                } catch (exception) { }
              }
              if (parseSupported) {
                try {
                  // FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal
                  // points. These environments, along with FF 3.1b1 and 2,
                  // also allow trailing commas in JSON objects and arrays.
                  parseSupported = parse("1.") !== 1;
                } catch (exception) { }
              }
            }
          }
        } catch (exception) {
          parseSupported = false;
        }
      }
      isSupported = parseSupported;
    }
  }
  return has[name] = !!isSupported;
}

function forEach(object, callback) {
  var size = 0, Properties, members, property;

  // Tests for bugs in the current environment's `for...in` algorithm. The
  // `valueOf` property inherits the non-enumerable flag from
  // `Object.prototype` in older versions of IE, Netscape, and Mozilla.
  (Properties = function() {
    this.valueOf = 0;
  }).prototype.valueOf = 0;

  // Iterate over a new instance of the `Properties` class.
  members = new Properties();
  for (property in members) {
    // Ignore all properties inherited from `Object.prototype`.
    if (isProperty.call(members, property)) {
      size++;
    }
  }
  Properties = members = null;

  // Normalize the iteration algorithm.
  if (!size) {
    // A list of non-enumerable properties inherited from `Object.prototype`.
    members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"];
    // IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
    // properties.
    forEach = function(object, callback) {
      var isFunction = Object.prototype.toString.call(object) == functionClass, property, length;
      var hasProperty = !isFunction && typeof object.constructor != "function" && objectTypes[typeof object.hasOwnProperty] && object.hasOwnProperty || isProperty;
      for (property in object) {
        // Gecko <= 1.0 enumerates the `prototype` property of functions under
        // certain conditions; IE does not.
        if (!(isFunction && property == "prototype") && hasProperty.call(object, property)) {
          callback(property);
        }
      }
      // Manually invoke the callback for each non-enumerable property.
      for (length = members.length; property = members[--length]; hasProperty.call(object, property) && callback(property));
    };
  } else if (size == 2) {
    // Safari <= 2.0.4 enumerates shadowed properties twice.
    forEach = function(object, callback) {
      // Create a set of iterated properties.
      var members = {}, isFunction = Object.prototype.toString.call(object) == functionClass, property;
      for (property in object) {
        // Store each property name to prevent double enumeration. The
        // `prototype` property of functions is not enumerated due to cross-
        // environment inconsistencies.
        if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) {
          callback(property);
        }
      }
    };
  } else {
    // No bugs detected; use the standard `for...in` algorithm.
    forEach = function(object, callback) {
      var isFunction = Object.prototype.toString.call(object) == functionClass, property, isConstructor;
      for (property in object) {
        if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) {
          callback(property);
        }
      }
      // Manually invoke the callback for the `constructor` property due to
      // cross-environment inconsistencies.
      if (isConstructor || isProperty.call(object, (property = "constructor"))) {
        callback(property);
      }
    };
  }
  return forEach(object, callback);
};

function isProperty(property) {
  var members = {}, constructor;
  if ((members.__proto__ = null, members.__proto__ = {
    // The *proto* property cannot be set multiple times in recent
    // versions of Firefox and SeaMonkey.
    "toString": 1
  }, members).toString != Object.prototype.toString) {
    // Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
    // supports the mutable *proto* property.
    isProperty = function(property) {
      // Capture and break the object's prototype chain (see section 8.6.2
      // of the ES 5.1 spec). The parenthesized expression prevents an
      // unsafe transformation by the Closure Compiler.
      var original = this.__proto__, result = property in (this.__proto__ = null, this);
      // Restore the original prototype chain.
      this.__proto__ = original;
      return result;
    };
  } else {
    // Capture a reference to the top-level `Object` constructor.
    constructor = members.constructor;
    // Use the `constructor` property to simulate `Object#hasOwnProperty` in
    // other environments.
    isProperty = function(property) {
      var parent = (this.constructor || constructor).prototype;
      return property in this && !(property in parent && this[property] === parent[property]);
    };
  }
  members = null;
  return isProperty.call(this, property);
};