@finos/legend-shared
Version:
Legend Studio shared utilities and helpers
142 lines • 6.85 kB
JavaScript
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { pruneNullValues } from '../CommonUtils.js';
import { custom, SKIP, deserialize, serialize, } from 'serializr';
export class SerializationFactory {
schema;
deserializeNullAsUndefined;
constructor(schema, options) {
this.schema = schema;
this.deserializeNullAsUndefined = Boolean(options?.deserializeNullAsUndefined);
}
toJson(val) {
return serialize(this.schema, val);
}
fromJson(val) {
return deserialize(this.schema, this.deserializeNullAsUndefined ? pruneNullValues(val) : val);
}
}
export const usingModelSchema = (schema) => custom((value) => (value === undefined ? SKIP : serialize(schema, value)), (value) => deserialize(schema, value));
// NOTE: we need these methods because `map()` of `serializr` tries to smartly
// determines if it should produce object or ES6 Map but we always want ES6 Map,
// so we would use this function
export const deserializeMap = (val, itemDeserializer) => {
const result = new Map();
Object.keys(val).forEach((key) => result.set(key, itemDeserializer(val[key])));
return result;
};
export const serializeMap = (val, itemSerializer) => {
const result = {};
val.forEach((v, key) => {
result[key] = itemSerializer(v);
});
return result;
};
export const usingConstantValueSchema = (value) => custom(() => value, () => value);
/**
* This is the idiomatic usage pattern for `optional(custom(...))`.
*
* `optional` only affects serialization so we must make sure to check
* if the value is `undefined` or not, if yes, serialize, else, return `undefined`
* which will be processed by `optional(...)` as `SKIP`.
*
* `optional` does not affect deserialization, however, as `undefined` values
* are automatically skipped
* See https://github.com/mobxjs/serializr/issues/73#issuecomment-535641545
*/
export const optionalCustom = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
serializer,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
deserializer, additionalArgs) => custom((val) => (val ? serializer(val) : SKIP), (val) => (val ? deserializer(val) : SKIP), additionalArgs);
export const optionalCustomUsingModelSchema = (schema) => custom((val) => (val ? serialize(schema, val) : SKIP), (val) => (val ? deserialize(schema, val) : SKIP));
export const deserializeArray = (values, itemDeserializer, options) => {
if (Array.isArray(values)) {
return values.map(itemDeserializer);
}
return options?.skipIfEmpty ? SKIP : [];
};
export const serializeArray = (values, itemSerializer, options) => {
let forceReturnEmptyInTest = false;
// NOTE: this block is meant for test, `webpack` will tree-shake it
// so we never reach inside, else we would get error about `process is not defined` as we're
// in browser environment
// eslint-disable-next-line no-process-env
if (process.env.NODE_ENV === 'test') {
forceReturnEmptyInTest =
Boolean(options?.INTERNAL__forceReturnEmptyInTest) &&
// TODO: when we distribute engine-roundtrip tests to different test groups, we should
// remove this condition and clean up test data accordingly.
// eslint-disable-next-line no-process-env
process.env.TEST_GROUP === 'engine-roundtrip';
}
if (Array.isArray(values)) {
return values.length
? values.map((value) => itemSerializer(value))
: forceReturnEmptyInTest
? []
: options?.skipIfEmpty
? SKIP
: [];
}
return forceReturnEmptyInTest ? [] : SKIP;
};
/**
* This is the idiomatic usage pattern for serialization of optional list of objects.
*
* Notice our particular usage of `serializeArray` and `deserializeArray` that is deisnged
* for testing and accounting for logic mismatches between servers and studio
*/
export const customListWithSchema = (schema, options) => custom((values) => serializeArray(values, (value) => serialize(schema, value), {
skipIfEmpty: true,
INTERNAL__forceReturnEmptyInTest: options?.INTERNAL__forceReturnEmptyInTest,
}), (values) => deserializeArray(values, (value) => deserialize(schema, value), {
skipIfEmpty: false,
}));
export const customList = (serializer, deserializer, options) => custom((values) => serializeArray(values, (value) => serializer(value), {
skipIfEmpty: true,
INTERNAL__forceReturnEmptyInTest: options?.INTERNAL__forceReturnEmptyInTest,
}), (values) => deserializeArray(values, (value) => deserializer(value), {
skipIfEmpty: false,
}));
export const customEquivalentList = (options) => customList((value) => value, (value) => value, options);
export const optionalCustomListWithSchema = (schema, options) => optionalCustom((values) => serializeArray(values, (value) => serialize(schema, value), {
skipIfEmpty: true,
INTERNAL__forceReturnEmptyInTest: options?.INTERNAL__forceReturnEmptyInTest,
}), (values) => deserializeArray(values, (value) => deserialize(schema, value), {
skipIfEmpty: false,
}));
export const optionalCustomList = (serializer, deserializer, options) => optionalCustom((values) => serializeArray(values, (value) => serializer(value), {
skipIfEmpty: true,
INTERNAL__forceReturnEmptyInTest: options?.INTERNAL__forceReturnEmptyInTest,
}), (values) => deserializeArray(values, (value) => deserializer(value), {
skipIfEmpty: false,
}));
/**
* NOTE: this is a workaround for `serializr` to avoid the magic extension mechanism provided
* by `createModelSchema`, where depending on the order schemas are defined, if the schema of the
* super class is specified first, when we serialize subclasses, we would get fields of the order
* of fields from the super classes first, followed by fields from the subclasses, not the order
* specified in the subclass's schema.
*
* See https://github.com/mobxjs/serializr/issues/179
*/
export const TEMPORARY__disableModelSchemaExtensionMechanism = (schema) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
schema.extends = undefined;
return schema;
};
//# sourceMappingURL=SerializationUtils.js.map