JSON with Maps (v8)

Revision 8 of this benchmark created on


Setup

let basic_obj = {
  a: true,
  b: ["a", "b", "cdef"],
  c: {
    d: new Map([
      ["a", 1],
      ["b", 1],
    ]),
  },
};

let tricky_obj = {
  a: new Map([
    ["@Array", 1],
    [{ _meta: "@Array" }, 1],
  ]),
  "@Set": new Set([[{ _meta: 1 }], [{ _meta: 2 }]]),
};

let long_array = ["@Array"];
for(let i = 0; i<10000; i++) {
	long_array.push(i);
}

function make_large_object(c = 4) {
  if (c <= 0) {
	return Math.random();
  }
  // Large arrays
  // Large nested objects
  let variants = [
    "object",
    "evil-object",
    "array",
    "evil-array",
    "map",
    "number",
    "string",
  ];
  let variant = variants[Math.floor(Math.random() * variants.length)];

  if (variant == "object" || variant == "evil-object") {
    let size = Math.floor(Math.random() * 10);
    let obj = {};
    if (variant == "evil-object") {
      obj["_meta"] = 3;
    }
    for (let i = 0; i < size; i++) {
      obj[make_large_object(c - 1)] = make_large_object(c - 1);
    }
    return obj;
  } else if (variant == "array" || variant == "evil-array") {
    let size = Math.floor(Math.random() * 500);
    let arr = [];
    if (variant == "evil-array") {
      arr.push("@Map");
    }
    for (let i = 0; i < size; i++) {
      arr.push(make_large_object(c - 3));
    }
    return arr;
  } else if (variant == "map") {
    let size = Math.floor(Math.random() * 100);
    let map = new Map();
    for (let i = 0; i < size; i++) {
      map.set(make_large_object(c - 2), make_large_object(c - 2));
    }
    return map;
  } else if (variant == "number") {
    return Math.random();
  } else if (variant == "string") {
    return Math.random().toString(36).substring(7);
  } else {
    throw "Unknown variant";
  }
}

let large_objects = [];
for (let i = 0; i < 10; i++) {
  large_objects.push(make_large_object());
}

function jsonMapSetReplacer(_, value_) {
  if (typeof value_ === "object") {
    if (value_ instanceof Map) {
      value_ = Array.from(value_);
      value_.unshift("@Map");
    } else if (value_ instanceof Set) {
      value_ = Array.from(value_);
      value_.unshift("@Set");
    } else if (
      Array.isArray(value_) &&
      value_.length > 0 &&
      (value_[0] === "@Map" ||
        value_[0] === "@Set" ||
        value_[0] === "@Array")
    ) {
      value_ = value_.slice();
      value_.unshift("@Array");
    }
  }

  return value_;
}

function jsonMapSetReviver(_, value_) {
  if (typeof value_ === "object") {
    if (Array.isArray(value_) && value_.length > 0) {
      var isMap, isSet;
      if (
        (isMap = value_[0] === "@Map") ||
        (isSet = value_[0] === "@Set") ||
        value_[0] === "@Array"
      ) {
        value_.shift();
        if (isMap) value_ = new Map(value_);
        else if (isSet) value_ = new Set(value_);
      }
    }
  }

  return value_;
}

function* withFirst(first, iter) {
  yield first;
  yield* iter;
}

function skipFirst(iter) {
  iter.next();
  return iter;
}

function jsonMapSetReplacer3(_, value_) {
  if (typeof value_ === "object") {
    if (value_ instanceof Map) {
      return Array.from(withFirst("@Map", value_));
    } else if (value_ instanceof Set) {
      return Array.from(withFirst("@Set", value_));
    } else if (
      Array.isArray(value_) &&
      value_.length > 0 &&
      (value_[0] === "@Map" || value_[0] === "@Set" || value_[0] === "@Array")
    ) {
      return Array.from(withFirst("@Set", value_));
    }
  }

  return value_;
}

function jsonMapSetReviver3(_, value_) {
  if (Array.isArray(value_) && value_.length > 0) {
    if (value_[0] === "@Array") {
      value_.shift();
    } else if (value_[0] === "@Map") {
      return new Map(skipFirst(value_.values()));
    } else if (value_[0] === "@Set") {
      return new Set(skipFirst(value_.values()));
    }
  }
  return value_;
}


Test runner

Ready to run.

Testing in
TestOps/sec
Array Based
let result = JSON.parse(JSON.stringify(basic_obj,jsonMapSetReplacer ),jsonMapSetReviver);
ready
Array Based 3
let result = JSON.parse(JSON.stringify(basic_obj,jsonMapSetReplacer3),jsonMapSetReviver3);
ready
Array Based
let result = JSON.parse(JSON.stringify(tricky_obj,jsonMapSetReplacer ),jsonMapSetReviver);
ready
Array Based 3
let result = JSON.parse(JSON.stringify(tricky_obj,jsonMapSetReplacer3),jsonMapSetReviver3);
ready
Array Based
for(let i = 0; i < large_objects.length; i++) {
  let result = JSON.parse(JSON.stringify(large_objects[i],jsonMapSetReplacer ),jsonMapSetReviver);
}
ready
Array Based 3
for(let i = 0; i < large_objects.length; i++) {
  let result = JSON.parse(JSON.stringify(large_objects[i],jsonMapSetReplacer3),jsonMapSetReviver3);
}
ready
Array Based
let result = JSON.parse(JSON.stringify(long_array,jsonMapSetReplacer ),jsonMapSetReviver);
ready
Array Based 3
let result = JSON.parse(JSON.stringify(long_array,jsonMapSetReplacer3),jsonMapSetReviver3);
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.