Preferred way to serialize/deserialize js-joda LocalDate?

847 views Asked by At

We are using js-joda LocalDate to represent various dates in our model and are storing those dates in sessionStorage. Is there a generalized preferred way of storing those dates so that they can serialize/deserialize without adding special code to each object that contains them?

We have been using the standard JSON.stringify / JSON.parse to do this, but since LocalDate converts to an ISO string when stringified, we lose its LocalDate type when we parse it back.

As demonstrated here

Here's the summary:

const myObj = { a: "thing", d: LocalDate.parse('2019-01-20') };
const stringified = JSON.stringify(myObj);
const parsed = JSON.parse(stringified);

// this fails because d is no longer a LocalDate
console.log(parsed.d.year());

Our workaround now is that we have custom deserializers for any class that contains a LocalDate, but it seems a little kludgy.

Seeking a cleaner solution for this. Perhaps we could make a generalized serializer for LocalDate that outputs the same thing as the %o modifier in console.log?

mydate -> serialize -> "LocalDate { _year: 2019, _month: 1, _day: 20}"

Before we do that, I'm looking to see if this has already been done cleanly or if I'm just missing something obvious.

1

There are 1 answers

0
Bernie B On

Answering my own question.

I'm surprised it hasn't come up, but the solution is right there in the definitions of JSON.stringify and JSON.parse.

This post pointed me to the solution when I needed to do the same thing with a Map.

JSON.parse(text[, reviver])
JSON.stringify(value[, replacer[, space]])

I needed to add replacers and revivers to do the custom serialization:

function myReviver(key: string, value: any) {
    if (value === undefined) return undefined;
    if (value === null) return null;
    if (typeof value === 'object') {
        switch (value.dataType) {
            case 'LocalDate':
                return LocalDate.parse(value.value);
            case 'LocalTime':
                return LocalTime.parse(value.value);
            case 'LocalDateTime':
                return LocalDateTime.parse(value.value);
            case 'Period':
                return Period.parse(value.value);
        }
    }
    return value;
}

function myReplacer(key, value) {
    const originalObject = this[key];
    if (originalObject instanceof LocalDate) {
        return {
            dataType: 'LocalDate',
            value: originalObject.toJSON()
        };
    } else if (originalObject instanceof LocalTime) {
        return {
            dataType: 'LocalTime',
            value: originalObject.toJSON()
        };
    } else if (originalObject instanceof LocalDateTime) {
        return {
            dataType: 'LocalDateTime',
            value: originalObject.toJSON()
        };
    } else if (originalObject instanceof Period) {
        return {
            dataType: 'Period',
            value: originalObject.toJSON()
        };
    } else {
        return value;
    }
}

Whenever I call stringify or parse, I add the above functions as their replacer/revivers.

JSON.stringify(mystuff, myReplacer);
JSON.parse(mystuff, myReviver);