Skip to content

Instantly share code, notes, and snippets.

@f-space
Created July 3, 2020 09:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save f-space/5aa705634c7609ee33d0356fd2bf7653 to your computer and use it in GitHub Desktop.
Save f-space/5aa705634c7609ee33d0356fd2bf7653 to your computer and use it in GitHub Desktop.
JSON serializer/deserializer for RPG Maker MV, which have data compatibility with those in the core script.
/*:ja
* @plugindesc JSONシリアライズの挙動を改善します。
* @author F_
*
* @help
* ツクールのセーブ/ロード時などに使用されるJSONシリアライザ/デシリアライザの挙動を改善します。
*
* ツクール標準のシリアライザは実行中に対象のオブジェクトを書き換え、
* 終了時にいくつかの操作によって元に近い状態へと戻します。
* この挙動は多くの場合に問題なく動作しますが、
* Proxy等によりシリアライズ時のデータを別のものに見せかけた場合にオブジェクトを破壊します。
*
* このプラグインのシリアライザでは対象オブジェクトを一切変更しません。
* そのため、このような問題は発生しません。
*
* また、ツクール標準のデシリアライザは高い優先度で Object.setPrototypeOf を使用して、
* プロトタイプの設定を行いますが、このAPIはJavaScriptエンジンの最適化を阻害し、
* プロパティアクセスの速度低下を起こす場合があります。
*
* このプラグインのデシリアライザでは代わりに Object.create を使用します。
*/
"use strict";
if (!Object.fromEntries) {
Object.fromEntries = iterable => {
const entries = Array.isArray(iterable) ? iterable : [...iterable];
return entries.reduce((result, [key, value]) => {
result[typeof key === 'symbol' ? key : String(key)] = value;
return result;
}, {});
};
}
{
const PROP_TYPE = "@";
const PROP_ID = "@c";
const PROP_ARRAY = "@a";
const PROP_REF = "@r";
JsonEx._encode = function (value) {
const maxDepth = this.maxDepth;
const encode = (value, map, depth) => {
if (depth > maxDepth) throw new Error(`recursion count exceeds the limit(${maxDepth})`);
if (typeof value !== 'object' || value === null) {
return value;
} else if (Array.isArray(value)) {
return refValue(value, map, id => ({
[PROP_ID]: id,
[PROP_ARRAY]: value.map(elem => encode(elem, map, depth + 1)),
}));
} else {
return refValue(value, map, id => {
const type = !isPlainObject(value) ? this._getConstructorName(value) : undefined;
return Object.assign(replaceValues(value, value => encode(value, map, depth + 1)), {
[PROP_TYPE]: type,
[PROP_ID]: id,
});
});
}
};
const refValue = (value, map, encoder) => {
const ref = map.get(value);
if (ref !== undefined) {
return { [PROP_REF]: ref };
} else {
const id = this._generateId();
map.set(value, id);
return encoder(id);
}
};
const isPlainObject = value => {
const proto = Object.getPrototypeOf(value);
return proto === Object.prototype || proto === Array.prototype;
};
const replaceValues = (obj, fn) =>
Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value)]));
return encode(value, new Map(), 0);
};
JsonEx._decode = function (value) {
const decode = (value, map, resolvers) => {
if (typeof value !== 'object' || value === null) {
return value;
} else if (value.hasOwnProperty(PROP_ARRAY)) {
const { [PROP_ID]: id, [PROP_ARRAY]: array } = value;
map.set(id, array);
for (const key of array.keys()) {
decodeProp(array, key, map, resolvers);
}
return array;
} else {
const { [PROP_TYPE]: type, [PROP_ID]: id, ...rest } = value;
const ctor = type !== undefined ? window[type] : undefined;
const obj = Object.assign(ctor !== undefined ? Object.create(ctor.prototype) : {}, rest);
map.set(id, obj);
for (const key of Object.keys(obj)) {
decodeProp(obj, key, map, resolvers);
}
return obj;
}
};
const decodeProp = (obj, key, map, resolvers) => {
const value = obj[key];
if (typeof value === 'object' && value !== null && value.hasOwnProperty(PROP_REF)) {
resolvers.push(() => obj[key] = map.get(value[PROP_REF]));
} else {
obj[key] = decode(value, map, resolvers);
}
};
const resolvers = [];
const result = decode(value, new Map(), resolvers);
resolvers.forEach(resolver => resolver());
return result;
};
JsonEx._cleanMetadata = function () { };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment