Repository: jquense/yup
Pull request #342 (2018-10-16 11:16:03)

fix: cast returns the origin value if there is nothing to cast
The current implementation of `cast` always returns a new instance of object/array/date value. It creates some problems when we want to know whether or not a value was really cast.


Patch:
diff --git a/src/array.js b/src/array.js
index 21e8a3c..29dceea 100644
--- a/src/array.js
+++ b/src/array.js
@@ -42,12 +42,22 @@ inherits(ArraySchema, MixedSchema, {
   },
 
   _cast(_value, _opts) {
-    var value = MixedSchema.prototype._cast.call(this, _value, _opts);
+    const value = MixedSchema.prototype._cast.call(this, _value, _opts);
 
     //should ignore nulls here
     if (!this._typeCheck(value) || !this._subType) return value;
 
-    return value.map(v => this._subType.cast(v, _opts));
+    let isChanged = false;
+    const castArray = value.map(v => {
+      const castElement = this._subType.cast(v, _opts);
+      if (castElement !== v) {
+        isChanged = true;
+      }
+
+      return castElement;
+    });
+
+    return isChanged ? castArray : value;
   },
 
   _validate(_value, options = {}) {
diff --git a/src/date.js b/src/date.js
index fc1cb47..08c1210 100644
--- a/src/date.js
+++ b/src/date.js
@@ -18,7 +18,7 @@ function DateSchema() {
 
   this.withMutation(() => {
     this.transform(function(value) {
-      if (this.isType(value)) return isDate(value) ? new Date(value) : value;
+      if (this.isType(value)) return value;
 
       value = isoParse(value);
       return value ? new Date(value) : invalidDate;
diff --git a/src/object.js b/src/object.js
index 092b728..61f6db7 100644
--- a/src/object.js
+++ b/src/object.js
@@ -88,6 +88,7 @@ inherits(ObjectSchema, MixedSchema, {
       __validating: false,
     };
 
+    let isChanged = false;
     props.forEach(prop => {
       let field = fields[prop];
       let exists = has(value, prop);
@@ -102,7 +103,10 @@ inherits(ObjectSchema, MixedSchema, {
 
         field = field.resolve(innerOptions);
 
-        if (field._strip === true) return;
+        if (field._strip === true) {
+          isChanged = true;
+          return;
+        }
 
         fieldValue =
           !options.__validating || !strict
@@ -111,8 +115,10 @@ inherits(ObjectSchema, MixedSchema, {
 
         if (fieldValue !== undefined) intermediateValue[prop] = fieldValue;
       } else if (exists && !strip) intermediateValue[prop] = value[prop];
+
+      if (intermediateValue[prop] !== value[prop]) isChanged = true;
     });
-    return intermediateValue;
+    return isChanged ? intermediateValue : value;
   },
 
   _validate(_value, opts = {}) {


Test patch:
diff --git a/test/object.js b/test/object.js
index 8b7c3c7..02acfb3 100644
--- a/test/object.js
+++ b/test/object.js
@@ -13,6 +13,19 @@ import {
 
 describe('Object types', () => {
   describe('casting', () => {
+    let inst;
+
+    beforeEach(() => {
+      inst = object({
+        num: number(),
+        str: string(),
+        arr: array().of(number()),
+        dte: date(),
+        nested: object().shape({ str: string() }),
+        arrNested: array().of(object().shape({ num: number() })),
+      });
+    });
+
     it('should parse json strings', () => {
       object({ hello: number() })
         .cast('{ "hello": "5" }')
@@ -35,23 +48,31 @@ describe('Object types', () => {
         arrNested: [{ num: 5 }, { num: '5' }],
       };
 
-      object({
-        num: number(),
-        str: string(),
-        arr: array().of(number()),
-        dte: date(),
-        nested: object().shape({ str: string() }),
-        arrNested: array().of(object().shape({ num: number() })),
-      })
-        .cast(obj)
-        .should.eql({
-          num: 5,
-          str: 'hello',
-          arr: [4, 5],
-          dte: new Date(1411500325000),
-          nested: { str: '5' },
-          arrNested: [{ num: 5 }, { num: 5 }],
-        });
+      const cast = inst.cast(obj);
+
+      cast.should.eql({
+        num: 5,
+        str: 'hello',
+        arr: [4, 5],
+        dte: new Date(1411500325000),
+        nested: { str: '5' },
+        arrNested: [{ num: 5 }, { num: 5 }],
+      });
+
+      cast.arrNested[0].should.equal(obj.arrNested[0], 'should be kept as is');
+    });
+
+    it('should return the same object if all props are already cast', () => {
+      let obj = {
+        num: 5,
+        str: 'hello',
+        arr: [4, 5],
+        dte: new Date(1411500325000),
+        nested: { str: '5' },
+        arrNested: [{ num: 5 }, { num: 5 }],
+      };
+
+      inst.cast(obj).should.equal(obj);
     });
   });