5
class MyClass {
  myString: string;
  myDate: Date;
}

function foo() {
  const myClassArray: MyClass[] = ....
  return JSON.stringify(myClassArray); // or expressApp.status(200).json(myClassArray);
}

foo will retun a json string with myDate a string following the ISO standard of YYYY-MM-DDThh:mm:ssZ.

I'd like to be able to customize how JSON.stringify() is serializing myDate.

I'm not stuck to JSON.stringify() to achieve what I want, although I prefer it since express.json() is using it behind the scene. In case this is not possible, is there a serialization library that will help me achieve what I want? Maybe a library that implements a decorator pattern? or maybe a more native solution?

Kam
  • 5,878
  • 10
  • 53
  • 97
  • `JSON.stringify` does not parse things...? – Heretic Monkey Mar 20 '20 at 19:08
  • An object that goes into JSON.stringify() ends up a string. Isn't that parsing? – Kam Mar 20 '20 at 19:09
  • No, that's serialization. You can use the second argument to [`stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) to include custom logic based on the type of value. – Heretic Monkey Mar 20 '20 at 19:10
  • I tried that, but the replacer function is very limited (or I didn't manage to figure it out). so for example, if I'm passing an array is different then when I'm passing the object itself. – Kam Mar 20 '20 at 19:12
  • Might want to [edit] your question with how you tried to use the replacer function. It might be something you're overlooking. – Heretic Monkey Mar 20 '20 at 19:14
  • [How to JSON stringify a javascript Date and preserve timezone](https://stackoverflow.com/q/31096130/215552) discusses overriding the `toJSON` method of the `Date` object to get a custom format. It *should* be using the ISO 8601 format already; not sure why you're getting something different. – Heretic Monkey Mar 20 '20 at 19:18
  • @HereticMonkey This is actually very helpful thank you. Do you think I can use the same technique to create a decorator. Whenever I annotate a Date property with it, it changes its toJson behavior? I'm new to TS/JS this is something I used to do in Java. Maybe this even already exists, in some library – Kam Mar 20 '20 at 19:21
  • That's a whole new question :). I would be hesitant to do that, since changing the `toJSON` function is done at the object level and applies to all `Date` objects, not at the property level. Theoretically, you could do something with decorators and the object's `toJSON` override; that would be an interesting technique to pursue. – Heretic Monkey Mar 20 '20 at 19:25

1 Answers1

2

You can use the replacer argument and pass in a function. There's probably a cleaner way to do this but this is a quick example:

const data = [{
    date: new Date(),
    id: 1
  },
  {
    date: new Date(),
    id: 2
  }
]


const a = JSON.stringify(data)
const b = JSON.stringify(data, replacer('date'))

function replacer(target) {
  return function(key, value) {
    if (key == target) {
      const month = new Date(value).getMonth()
      return `Date month: ${month}`
    }
    return value
  }
}

// [{"date":"2020-03-20T19:13:11.594Z","id":1},{"date":"2020-03-20T19:13:11.594Z","id":2}]
console.log(a)

// [{"date":"Date month: 2","id":1},{"date":"Date month: 2","id":2}]
console.log(b)

Edit

My TypeScript decorator kung fu isn't the strongest so if anyone has any suggestions feel free to point out improvements.

As a decorator:

@transformDate
class MyClass {
  date: Date;
  id: number;

  constructor(date, id) {
    this.date = date;
    this.id = id;
  }
}

function transformDate(target: any) {
  const formatted = new Intl.DateTimeFormat("en", {
    year: "numeric",
    month: "short",
    day: "2-digit"
  }).format(this.date);

  target.prototype.toJSON = function() {
    return {
      ...this,
      date: formatted
    };
  };

  return target;
}

const data = [new MyClass(new Date(), 1), new MyClass(new Date(), 2)];

// [{"date":"Mar 20, 2020","id":1},{"date":"Mar 20, 2020","id":2}]
console.log(JSON.stringify(data));

Phix
  • 9,364
  • 4
  • 35
  • 62
  • I guess I had a bug in my code as this should work. Is there a decorator pattern I can follow? to annotate the class directly? this way I don't have do this with every class I have? – Kam Mar 20 '20 at 19:18
  • @Kam I added a decorator example – Phix Mar 20 '20 at 19:51