1

I have a complex object

{
  "original": {
    "assetName": "2669937-cherry-blossoms-wallpapers.jpg",
    "tags": "",
    "id": 5834
  },
  "uploadState": {
    "status": 3
  },
  "file": {
    "isThumbnailable": false,
    "name": "2669937-cherry-blossoms-wallpapers.jpg",
    "tags": []
  },
  "customFields": [{
    "customFormApplicationId": 2014,
    "customFieldId": 1017,
    "referenceId": 0,
    "referenceType": 0,
    "label": "qaa",
    "orderId": 0,
    "type": 1,
    "value": "",
    "defaultValue": "",
    "properties": "MULTILINE:false|WATERMARK_TEXT:",
    "dateCreated": "0001-01-01T00:00:00",
    "isRequired": true,
    "$$hashKey": "object:22760",
    "requiredValueSet": false
  }, {
    "customFormApplicationId": 2014,
    "customFieldId": 1018,
    "referenceId": 0,
    "referenceType": 0,
    "label": "ddd",
    "orderId": 1,
    "type": 3,
    "properties": "MULTILINE:true|WATERMARK_TEXT:|VISIBLE_LINES:5|DISPLAY_TYPE:1|DATE_FORMAT:1|TIME_FORMAT:1",
    "dateCreated": "0001-01-01T00:00:00",
    "isRequired": true,
    "$$hashKey": "object:22761",
    "isSet": true,
    "value": "",
    "requiredValueSet": false
  }, {
    "customFormApplicationId": 2014,
    "customFieldId": 2017,
    "referenceId": 0,
    "referenceType": 0,
    "label": "drop",
    "orderId": 2,
    "type": 2,
    "value": "",
    "defaultValue": "",
    "properties": "MULTILINE:true|WATERMARK_TEXT:|VISIBLE_LINES:5|ITEMS:v1,v2,v3|DISPLAY_TYPE:1",
    "dateCreated": "0001-01-01T00:00:00",
    "isRequired": false,
    "$$hashKey": "object:22762"
  }],
  "$$hashKey": "object:16951"
}

with dynamic structures.

I have to deep clone the object and I am using this method

var clone = $.parseJSON(JSON.stringify(original));

This is the only thing that actually worked so no other method can be used. The problem is that Date objects are converted to string So instead of

"dateCreated": Mon Jan 21 2019 13:45:06 GMT-0500 (Eastern Standard Time)
__proto__: Object,

I have "dateCreated":"2019-01-21T18:45:06.696Z"

To convert that back to Date I use

clone.dateCreated = new Date(original.dateCreated )

THE PROBLEM is that my object is very complex and has dynamic properties so I don't know the structure of the object.

What I need is to write function that will run on original object and check each property and if that property is Date type then go to clone to the same property and convert that string to Date

How the function should look like? Two key issues:

1 - run over all properties of the original object and check type 2 - find the same property in clone object

given they have the same structure

I am using ES5 and no lodash or underscore library

RobG
  • 142,382
  • 31
  • 172
  • 209
Polina F.
  • 629
  • 13
  • 32
  • 1
    did you even tried to solve this? do you have a code to check if you are at least close to solve it. – Prince Hernandez Jan 21 '19 at 19:06
  • Yes, your solution sounds fine, you seem to know exactly what the code will look like. Please show us your attempt. What's the part you're having problems with? – Bergi Jan 21 '19 at 19:15
  • 1
    Looks like you need to use a real deep clone algo. – kemicofa ghost Jan 21 '19 at 19:18
  • Possible duplicate of [How to Deep clone in javascript](https://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript) – kemicofa ghost Jan 21 '19 at 19:18
  • Not a duplicate , I have problem with dates – Polina F. Jan 21 '19 at 20:21
  • that is the way a date is stored as a string, you cant stringify a function (like dates) – john Smith Jan 21 '19 at 20:29
  • I think you're missing that a decent copy method will avoid your issue with Dates, e.g. [*How do I correctly clone a JavaScript object?*](https://stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object), which I think has a much better solution than kemicofa's link. Using `JSON.parse(JSON.stringify))` is seriously flawed as it will not handle certain types (e.g. *undefined*, *infinity*) or instances of built-in objects. It's meant as a data transfer mechanism, not a way of serialising objects in general. – RobG Jan 21 '19 at 21:01
  • @johnSmith—you can stringify functions, just not with JSON.stringify. ;-) – RobG Jan 21 '19 at 21:02

2 Answers2

0

I ended up doing angular.copy of the object, and since the File object doesn't copies correctly I just override it with original File

var clone = angular.copy(original);
clone.file = original.file
Polina F.
  • 629
  • 13
  • 32
  • So you took the advice of seeking a better copy mechanism. Good. :-) If that solved your problem you should accept your answer so the question is marked as answered. – RobG Jan 23 '19 at 02:07
-1

If you're insistent on using JSON.parse(JSON.stringify) then the following will recurse over the resulting object and convert any string values that match the regular expression to Dates.

It only deals with the likely types resulting from the JSON processing, if you need anything more sophisticated you just need more is… tests and following logic. Hopefully the comments are sufficient.

// Parse ISO 8601 date YYYY-MM-DDTHH:mm:ssZ
// Z is optional, indicates UTC
// Parses '0001' as 0001 not 1901
function toDate(s) {
  var b = s.split(/\D/);
  var d = new Date(0);
  if (/z$/i.test(s)) {
    d.setUTCFullYear(b[0], b[1]-1, b[2]);
    d.setUTCHours(b[3], b[4], b[5], b[6]||0);
    return d;
  } else {
    d.setFullYear(b[0], b[1]-1, b[2]);
    d.setHours(b[3], b[4], b[5], b[6]||0);
    return d;
  }
}

// Recurse over objects, if any property value is a string that
// matches the ISO 8601 pattern /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?$/
// convert it to a Date object
function stringsToDates(obj) {

  // Simple isObject and isArray functions
  let isObj = value => Object.prototype.toString.call(value) == '[object Object]';
  let isArr = value => Array.isArray(value);
  // Test if value is string date, object or array and convert, recurse or ignore
  let resolveValue = (value, key, obj) => {
    if (typeof value == 'string' && re.test(value)) {
      obj[key] = toDate(value);
    } else if (isArr(value) || isObj(value)) {
      go(value);
    }
  }

  // Regular expression for ISO date string
  var re = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?$/;
  
  // Recursive function looking for strings matching re
  function go(obj) {

    if (isArr(obj)) {
      obj.forEach((value, i, arr) => resolveValue(value, i, arr));
      
    } else if (isObj(obj)) {
      Object.keys(obj).forEach(key => resolveValue(obj[key], key, obj))
    }
  }
  return go(obj);
}

// Test data
var x = {
  aDate: '0001-01-01T00:00:00',  // level 0
  bool: true,
  obj: {arr: ['0002-01-01T00:00:00z', 'string', false]}, // level 2
  arr: ['0003-01-01T00:00:00', null, true, []], // level 1
  obj1: {
    nest: {
      deep: {
        string: 'foo',
        date0: '0004-01-01T00:00:00Z',  // level 4
        date1: '0005-01-01T00:00:00',  // level 4
        arr1: ['0006-01-01T00:00:00z', NaN, {}]  // level 5
      }
    }
  }
}

// Do conversion
stringsToDates(x);
console.log(x);
RobG
  • 142,382
  • 31
  • 172
  • 209
  • Nice solution, I was thinking to use property types of the original object and to run simultaneously on both original and clone.Check if the value of original is date like that one Date.prototype.isPrototypeOf(myDateObject) – Polina F. Jan 22 '19 at 14:09