serializeObject = (info, dynamicInfo) => {
	const obj = /** @type {T} */ ({});
	// Setup byProperty structure
	for (const entry of info.values()) {
		if (entry.byProperty !== undefined) {
			const byProperty = /** @type {keyof T} */ (entry.byProperty);
			const byObj = (obj[byProperty] =
				obj[byProperty] || /** @type {TODO} */ ({}));
			for (const byValue of entry.byValues.keys()) {
				byObj[byValue] = byObj[byValue] || {};
			}
		}
	}
	for (const [key, entry] of info) {
		if (entry.base !== undefined) {
			obj[/** @type {keyof T} */ (key)] = entry.base;
		}
		// Fill byProperty structure
		if (entry.byProperty !== undefined) {
			const byProperty = /** @type {keyof T} */ (entry.byProperty);
			const byObj = (obj[byProperty] =
				obj[byProperty] || /** @type {TODO} */ ({}));
			for (const byValue of Object.keys(byObj)) {
				const value = getFromByValues(entry.byValues, byValue);
				if (value !== undefined) byObj[byValue][key] = value;
			}
		}
	}
	if (dynamicInfo !== undefined) {
		/** @type {TODO} */
		(obj)[dynamicInfo.byProperty] = dynamicInfo.fn;
	}
	return obj;
}
@template {object} T
@param {Map<string, ObjectParsedPropertyEntry>} info static properties (key is property name)
@param {{ byProperty: string, fn: (...args: EXPECTED_ANY[]) => T } | undefined} dynamicInfo dynamic part
@returns {T} the object