我想在 HTML5 localStorage
中存储一个 JavaScript 对象,但我的对象显然正在转换为字符串。
我可以使用 localStorage
存储和检索原始 JavaScript 类型和数组,但对象似乎不起作用。他们应该吗?
这是我的代码:
var testObject = { 'one': 1, 'two': 2, 'three': 3 };
console.log('typeof testObject: ' + typeof testObject);
console.log('testObject properties:');
for (var prop in testObject) {
console.log(' ' + prop + ': ' + testObject[prop]);
}
// Put the object into storage
localStorage.setItem('testObject', testObject);
// Retrieve the object from storage
var retrievedObject = localStorage.getItem('testObject');
console.log('typeof retrievedObject: ' + typeof retrievedObject);
console.log('Value of retrievedObject: ' + retrievedObject);
控制台输出是
typeof testObject: object
testObject properties:
one: 1
two: 2
three: 3
typeof retrievedObject: string
Value of retrievedObject: [object Object]
在我看来,setItem
方法在存储之前将输入转换为字符串。
我在 Safari、Chrome 和 Firefox 中看到了这种行为,因此我认为这是我对 HTML5 Web Storage 规范的误解,而不是特定于浏览器的错误或限制。
我试图理解 2 Common infrastructure 中描述的 结构化克隆 算法。我不完全理解它在说什么,但也许我的问题与我的对象的属性不可枚举(???)有关。
有简单的解决方法吗?
更新:W3C 最终改变了对结构化克隆规范的看法,并决定更改规范以匹配实现。请参阅 12111 – spec for Storage object getItem(key) method does not match implementation behavior。所以这个问题不再是 100% 有效,但答案仍然可能是有趣的。
查看 Apple、Mozilla 和 Mozilla again 文档,该功能似乎仅限于处理字符串键/值对。
一种解决方法可以是在存储对象之前 stringify 您的对象,然后在您检索它时对其进行解析:
var testObject = { 'one': 1, 'two': 2, 'three': 3 };
// Put the object into storage
localStorage.setItem('testObject', JSON.stringify(testObject));
// Retrieve the object from storage
var retrievedObject = localStorage.getItem('testObject');
console.log('retrievedObject: ', JSON.parse(retrievedObject));
对 variant 的小改进:
Storage.prototype.setObject = function(key, value) {
this.setItem(key, JSON.stringify(value));
}
Storage.prototype.getObject = function(key) {
var value = this.getItem(key);
return value && JSON.parse(value);
}
由于 short-circuit evaluation,如果 key
不在存储中,getObject()
将立即返回 null
。如果 value
是 ""
(空字符串;JSON.parse()
无法处理),它也不会抛出 SyntaxError
异常。
var userObject = { userId: 24, name: 'Jack Bauer' };
并设置它localStorage.setObject('user', userObject);
然后从存储中取回userObject = localStorage.getObject('user');
如果需要,您甚至可以存储对象数组。
key
不在本地存储中,则 window.localStorage.getItem(key)
返回 null
——它确实不抛出“非法访问”异常——并且 JSON.parse(null)
也返回 null
——它确实not 也不会抛出异常,无论是在 Chromium 21 中还是根据 ES 5.1 section 15.12.2,因为 String(null) === "null"
可以解释为 JSON literal。
""
(空字符串)。因为它类型转换为 false
并且 JSON.parse("")
会引发 SyntaxError
异常,所以不会被调用。
您可能会发现使用这些方便的方法扩展 Storage 对象很有用:
Storage.prototype.setObject = function(key, value) {
this.setItem(key, JSON.stringify(value));
}
Storage.prototype.getObject = function(key) {
return JSON.parse(this.getItem(key));
}
通过这种方式,您可以获得您真正想要的功能,即使在 API 下仅支持字符串。
localStorage.setObject
。
""
,则此 getObject()
将引发 SyntaxError
异常,因为 JSON.parse()
无法处理。有关详细信息,请参阅我对 Guria 答案的编辑。
setObject
方法的浏览器不兼容。
为 Storage 对象创建外观是一个很棒的解决方案。这样,您可以实现自己的 get
和 set
方法。对于我的 API,我为 localStorage 创建了一个外观,然后在设置和获取时检查它是否是一个对象。
var data = {
set: function(key, value) {
if (!key || !value) {return;}
if (typeof value === "object") {
value = JSON.stringify(value);
}
localStorage.setItem(key, value);
},
get: function(key) {
var value = localStorage.getItem(key);
if (!value) {return;}
// assume it is an object that has been stringified
if (value[0] === "{") {
value = JSON.parse(value);
}
return value;
}
}
data.set('username': 'ifedi', 'fullname': { firstname: 'Ifedi', lastname: 'Okonkwo'});
?
if (!key || value === undefined) return;
这也可以让您为键存储一个“null”值。
Stringify 并不能解决所有问题
似乎这里的答案并没有涵盖 JavaScript 中所有可能的类型,所以这里有一些关于如何正确处理它们的简短示例:
// Objects and Arrays:
var obj = {key: "value"};
localStorage.object = JSON.stringify(obj); // Will ignore private members
obj = JSON.parse(localStorage.object);
// Boolean:
var bool = false;
localStorage.bool = bool;
bool = (localStorage.bool === "true");
// Numbers:
var num = 42;
localStorage.num = num;
num = +localStorage.num; // Short for "num = parseFloat(localStorage.num);"
// Dates:
var date = Date.now();
localStorage.date = date;
date = new Date(parseInt(localStorage.date));
// Regular expressions:
var regex = /^No\.[\d]*$/i; // Usage example: "No.42".match(regex);
localStorage.regex = regex;
var components = localStorage.regex.match("^/(.*)/([a-z]*)$");
regex = new RegExp(components[1], components[2]);
// Functions (not recommended):
function func() {}
localStorage.func = func;
eval(localStorage.func); // Recreates the function with the name "func"
我不建议存储函数,因为 eval()
是邪恶的,可能会导致安全、优化和调试方面的问题。
通常,eval()
绝不应在 JavaScript 代码中使用。
私人会员
使用 JSON.stringify()
存储对象的问题是,该函数不能序列化私有成员。
此问题可以通过覆盖 .toString()
方法(在网络存储中存储数据时隐式调用)来解决:
// Object with private and public members:
function MyClass(privateContent, publicContent) {
var privateMember = privateContent || "defaultPrivateValue";
this.publicMember = publicContent || "defaultPublicValue";
this.toString = function() {
return '{"private": "' + privateMember + '", "public": "' + this.publicMember + '"}';
};
}
MyClass.fromString = function(serialisedString) {
var properties = JSON.parse(serialisedString || "{}");
return new MyClass(properties.private, properties.public);
};
// Storing:
var obj = new MyClass("invisible", "visible");
localStorage.object = obj;
// Loading:
obj = MyClass.fromString(localStorage.object);
循环引用
stringify
无法处理的另一个问题是循环引用:
var obj = {};
obj["circular"] = obj;
localStorage.object = JSON.stringify(obj); // Fails
在此示例中,JSON.stringify()
将抛出 TypeError
“将循环结构转换为 JSON”。
如果应支持存储循环引用,则可以使用 JSON.stringify()
的第二个参数:
var obj = {id: 1, sub: {}};
obj.sub["circular"] = obj;
localStorage.object = JSON.stringify(obj, function(key, value) {
if(key == 'circular') {
return "$ref" + value.id + "$";
} else {
return value;
}
});
但是,找到存储循环引用的有效解决方案在很大程度上取决于需要解决的任务,并且恢复此类数据也并非易事。
Stack Overflow 上已经有一些问题处理这个问题:Stringify (convert to JSON) a JavaScript object with circular reference
localStorage.num
(num = +localStorage.num
) 前面加“+”?
有一个很棒的库,其中包含许多解决方案,因此它甚至支持称为 jStorage 的旧浏览器
你可以设置一个对象
$.jStorage.set(key, value)
并轻松检索
value = $.jStorage.get(key)
value = $.jStorage.get(key, "default value")
我在点击另一个已关闭的帖子后到达此帖子 - 标题为“如何在本地存储中存储数组?”。这很好,除了两个线程实际上都没有提供关于如何在 localStorage 中维护数组的完整答案 - 但是我已经设法根据两个线程中包含的信息制定了一个解决方案。
因此,如果其他人希望能够在数组中推送/弹出/移动项目,并且他们希望将该数组存储在 localStorage 或实际上是 sessionStorage 中,那么您可以这样做:
Storage.prototype.getArray = function(arrayName) {
var thisArray = [];
var fetchArrayObject = this.getItem(arrayName);
if (typeof fetchArrayObject !== 'undefined') {
if (fetchArrayObject !== null) { thisArray = JSON.parse(fetchArrayObject); }
}
return thisArray;
}
Storage.prototype.pushArrayItem = function(arrayName,arrayItem) {
var existingArray = this.getArray(arrayName);
existingArray.push(arrayItem);
this.setItem(arrayName,JSON.stringify(existingArray));
}
Storage.prototype.popArrayItem = function(arrayName) {
var arrayItem = {};
var existingArray = this.getArray(arrayName);
if (existingArray.length > 0) {
arrayItem = existingArray.pop();
this.setItem(arrayName,JSON.stringify(existingArray));
}
return arrayItem;
}
Storage.prototype.shiftArrayItem = function(arrayName) {
var arrayItem = {};
var existingArray = this.getArray(arrayName);
if (existingArray.length > 0) {
arrayItem = existingArray.shift();
this.setItem(arrayName,JSON.stringify(existingArray));
}
return arrayItem;
}
Storage.prototype.unshiftArrayItem = function(arrayName,arrayItem) {
var existingArray = this.getArray(arrayName);
existingArray.unshift(arrayItem);
this.setItem(arrayName,JSON.stringify(existingArray));
}
Storage.prototype.deleteArray = function(arrayName) {
this.removeItem(arrayName);
}
示例用法 - 在 localStorage 数组中存储简单字符串:
localStorage.pushArrayItem('myArray','item one');
localStorage.pushArrayItem('myArray','item two');
示例用法 - 在 sessionStorage 数组中存储对象:
var item1 = {}; item1.name = 'fred'; item1.age = 48;
sessionStorage.pushArrayItem('myArray',item1);
var item2 = {}; item2.name = 'dave'; item2.age = 22;
sessionStorage.pushArrayItem('myArray',item2);
操作数组的常用方法:
.pushArrayItem(arrayName,arrayItem); -> adds an element onto end of named array
.unshiftArrayItem(arrayName,arrayItem); -> adds an element onto front of named array
.popArrayItem(arrayName); -> removes & returns last array element
.shiftArrayItem(arrayName); -> removes & returns first array element
.getArray(arrayName); -> returns entire array
.deleteArray(arrayName); -> removes entire array from storage
理论上,可以使用函数存储对象:
function store (a)
{
var c = {f: {}, d: {}};
for (var k in a)
{
if (a.hasOwnProperty(k) && typeof a[k] === 'function')
{
c.f[k] = encodeURIComponent(a[k]);
}
}
c.d = a;
var data = JSON.stringify(c);
window.localStorage.setItem('CODE', data);
}
function restore ()
{
var data = window.localStorage.getItem('CODE');
data = JSON.parse(data);
var b = data.d;
for (var k in data.f)
{
if (data.f.hasOwnProperty(k))
{
b[k] = eval("(" + decodeURIComponent(data.f[k]) + ")");
}
}
return b;
}
但是,函数序列化/反序列化是不可靠的,因为 it is implementation-dependent。
c.f[k] = escape(a[k]);
替换为 Unicode 安全的 c.f[k] = encodeURIComponent(a[k]);
,并将 eval('b.' + k + ' = ' + unescape(data.f[k]));
替换为 b[k] = eval("(" + decodeURIComponent(data.f[k]) + ")");
。括号是必需的,因为如果您的函数正确序列化,则可能是匿名的,这不是有效的 /Statement/(因此 eval()
)否则会抛出 SyntaxError
异常)。
typeof
是一个运算符,不要把它写成一个函数。将 typeof(a[k])
替换为 typeof a[k]
。
for
-in
未针对自己的属性进行过滤。 3. 代码风格,包括引用,不一致。
the use and placement of white space, line terminators, and semicolons within the representation String is implementation-dependent.
我没有看到任何功能差异。
Note *in particular* that …
开头。但是返回值规范以 An implementation-dependent representation of the function is returned. This representation has the syntax of a FunctionDeclaration.
开头。返回值可以是 function foo () {}
——假设是 conforming 实现。
建议对这里讨论的许多特性使用抽象库,以及更好的兼容性。有很多选择:
jStorage 或 simpleStorage ← 我的偏好
本地饲料
阿列克谢库利科夫/存储
草坪椅
Store.js ← 另一个不错的选择
我的天啊
本地数据存储
您不能存储没有字符串格式的键值。
LocalStorage 仅支持键/值的字符串格式。
这就是为什么您应该将数据转换为字符串,无论它是数组还是对象。
要将数据存储在 localStorage 中,首先使用 JSON.stringify() 方法对其进行字符串化。
var myObj = [{name:"test", time:"Date 2017-02-03T08:38:04.449Z"}];
localStorage.setItem('item', JSON.stringify(myObj));
然后当你想检索数据时,你需要再次将字符串解析为对象。
var getObj = JSON.parse(localStorage.getItem('item'));
您可以使用 localDataStorage 透明地存储 JavaScript 数据类型(Array、Boolean、Date、Float、Integer、String 和 Object)。它还提供轻量级的数据混淆,自动压缩字符串,促进键(名称)查询和(键)值查询,并通过为键加前缀来帮助在同一域内强制执行分段共享存储。
[免责声明] 我是该实用程序的作者 [/DISCLAIMER]
例子:
localDataStorage.set( 'key1', 'Belgian' )
localDataStorage.set( 'key2', 1200.0047 )
localDataStorage.set( 'key3', true )
localDataStorage.set( 'key4', { 'RSK' : [1,'3',5,'7',9] } )
localDataStorage.set( 'key5', null )
localDataStorage.get( 'key1' ) // --> 'Belgian'
localDataStorage.get( 'key2' ) // --> 1200.0047
localDataStorage.get( 'key3' ) // --> true
localDataStorage.get( 'key4' ) // --> Object {RSK: Array(5)}
localDataStorage.get( 'key5' ) // --> null
如您所见,原始值受到尊重。
您可以使用 ejson 将对象存储为字符串。
EJSON 是 JSON 的扩展,支持更多类型。它支持所有 JSON 安全类型,以及: Date (JavaScript Date) Binary (JavaScript Uint8Array 或 EJSON.newBinary 的结果) 用户定义的类型(参见 EJSON.addType。例如,Mongo.ObjectID 就是以这种方式实现的。 ) 所有 EJSON 序列化也是有效的 JSON。例如,具有日期和二进制缓冲区的对象将在 EJSON 中序列化为: { "d": {"$date": 1358205756553}, "b": {"$binary": "c3VyZS4="} }
这是我使用 ejson 的 localStorage 包装器
https://github.com/UziTech/storage.js
我在包装器中添加了一些类型,包括正则表达式和函数
另一种选择是使用现有的插件。
例如,persisto 是一个开源项目,它为 localStorage/sessionStorage 提供了一个简单的接口,并自动化了表单字段(输入、单选按钮和复选框)的持久性。
https://i.stack.imgur.com/cU5V0.png
(免责声明:我是作者。)
localStroage
的所有传统方法; exp: var lsh = new localStorageHelper(); lsh.setItem('bob', 'bill');
还包括事件。
对于愿意设置和获取类型化属性的 TypeScript 用户:
/**
* Silly wrapper to be able to type the storage keys
*/
export class TypedStorage<T> {
public removeItem(key: keyof T): void {
localStorage.removeItem(key);
}
public getItem<K extends keyof T>(key: K): T[K] | null {
const data: string | null = localStorage.getItem(key);
return JSON.parse(data);
}
public setItem<K extends keyof T>(key: K, value: T[K]): void {
const data: string = JSON.stringify(value);
localStorage.setItem(key, data);
}
}
// write an interface for the storage
interface MyStore {
age: number,
name: string,
address: {city:string}
}
const storage: TypedStorage<MyStore> = new TypedStorage<MyStore>();
storage.setItem("wrong key", ""); // error unknown key
storage.setItem("age", "hello"); // error, age should be number
storage.setItem("address", {city:"Here"}); // ok
const address: {city:string} = storage.getItem("address");
https://github.com/adrianmay/rhaboo 是一个 localStorage 糖层,可让您编写如下内容:
var store = Rhaboo.persistent('Some name');
store.write('count', store.count ? store.count+1 : 1);
store.write('somethingfancy', {
one: ['man', 'went'],
2: 'mow',
went: [ 2, { mow: ['a', 'meadow' ] }, {} ]
});
store.somethingfancy.went[1].mow.write(1, 'lawn');
它不使用 JSON.stringify/parse ,因为这在大对象上会不准确且速度慢。相反,每个终端值都有自己的 localStorage 条目。
您可能会猜到我可能与 rhaboo 有关。
localStorage.setItem('obj',JSON.stringify({name:'Akash'})); // Set Object in localStorage
localStorage.getItem('obj'); // Get Object from localStorage
sessionStorage.setItem('obj',JSON.stringify({name:'Akash'})); // Set Object in sessionStorage
sessionStorage.getItem('obj'); // Get Object from sessionStorage
我制作了另一个只有 20 行代码的简约包装器,以允许像应有的那样使用它:
localStorage.set('myKey',{a:[1,2,5], b: 'ok'});
localStorage.has('myKey'); // --> true
localStorage.get('myKey'); // --> {a:[1,2,5], b: 'ok'}
localStorage.keys(); // --> ['myKey']
localStorage.remove('myKey');
https://github.com/zevero/simpleWebstorage
我做了一个不会破坏现有存储对象的东西,而是创建了一个包装器,这样你就可以做你想做的事了。结果是一个普通对象,没有方法,可以像任何对象一样访问。
如果您希望 1 localStorage
属性具有魔力:
var prop = ObjectStorage(localStorage, 'prop');
如果你需要几个:
var storage = ObjectStorage(localStorage, ['prop', 'more', 'props']);
您对 prop
或 内部 storage
的对象所做的一切都会自动保存到 localStorage
。你总是在玩一个真实的对象,所以你可以做这样的事情:
storage.data.list.push('more data');
storage.another.list.splice(1, 2, {another: 'object'});
并且被跟踪对象内的每个新对象都将被自动跟踪。
最大的缺点:它依赖于 Object.observe()
,因此它对浏览器的支持非常有限。而且它看起来不会很快出现在 Firefox 或 Edge 上。
Object.observe
现在在所有主要浏览器中都已弃用。
我找到了一种使它与具有循环引用的对象一起工作的方法。
让我们用循环引用创建一个对象。
obj = {
L: {
L: { v: 'lorem' },
R: { v: 'ipsum' }
},
R: {
L: { v: 'dolor' },
R: {
L: { v: 'sit' },
R: { v: 'amet' }
}
}
}
obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;
由于循环引用,我们不能在此处执行 JSON.stringify
。
https://i.stack.imgur.com/4taQx.png
LOCALSTORAGE.CYCLICJSON
具有 .stringify
和 .parse
就像普通的 JSON
一样,但适用于具有循环引用的对象。 (“Works”意味着 parse(stringify(obj)) 和 obj 是深度相等的,并且具有相同的“内部相等”集)
但我们可以只使用快捷方式:
LOCALSTORAGE.setObject('latinUncles', obj)
recovered = LOCALSTORAGE.getObject('latinUncles')
那么,recovered
将与 obj “相同”,在以下意义上:
[
obj.L.L.v === recovered.L.L.v,
obj.L.R.v === recovered.L.R.v,
obj.R.L.v === recovered.R.L.v,
obj.R.R.L.v === recovered.R.R.L.v,
obj.R.R.R.v === recovered.R.R.R.v,
obj.R.L.uncle === obj.L,
obj.R.R.uncle === obj.L,
obj.R.R.L.uncle === obj.R.L,
obj.R.R.R.uncle === obj.R.L,
obj.L.L.uncle === obj.R,
obj.L.R.uncle === obj.R,
recovered.R.L.uncle === recovered.L,
recovered.R.R.uncle === recovered.L,
recovered.R.R.L.uncle === recovered.R.L,
recovered.R.R.R.uncle === recovered.R.L,
recovered.L.L.uncle === recovered.R,
recovered.L.R.uncle === recovered.R
]
这是 LOCALSTORAGE
的实现
LOCALSTORAGE = (function(){ "use strict"; var ignore = [Boolean, Date, Number, RegExp, String]; function primitive(item){ if (typeof item === 'object'){ if (item == = null) { return true; } for (var i=0; i
这个问题已经从纯 JavaScript 的角度得到了充分的回答,其他人已经注意到 localStorage.getItem
和 localStorage.setItem
都没有对象的概念——它们只处理字符串和字符串。此答案提供了一个 TypeScript 友好的解决方案,该解决方案将 others have suggested 包含在纯 JavaScript 解决方案中。
打字稿 4.2.3
Storage.prototype.setObject = function (key: string, value: unknown) {
this.setItem(key, JSON.stringify(value));
};
Storage.prototype.getObject = function (key: string) {
const value = this.getItem(key);
if (!value) {
return null;
}
return JSON.parse(value);
};
declare global {
interface Storage {
setObject: (key: string, value: unknown) => void;
getObject: (key: string) => unknown;
}
}
用法
localStorage.setObject('ages', [23, 18, 33, 22, 58]);
localStorage.getObject('ages');
解释
我们在 Storage
原型上声明了 setObject
和 getObject
函数——localStorage
是这种类型的一个实例。除了 getObject
中的 null 处理之外,没有什么特别需要注意的。由于 getItem
可以返回 null
,我们必须提前退出,因为对 null
值调用 JSON.parse
会引发运行时异常。
在 Storage
原型上声明函数后,我们将它们的类型定义包含在全局命名空间中的 Storage
类型上。
注意:如果我们用箭头函数定义这些函数,我们需要假设我们调用的存储对象总是 localStorage
,这可能不是真的。例如,上面的代码也会为 sessionStorage
添加 setObject
和 getObject
支持。
这是代码 posted by danott 的一些扩展版本:
它还将从 localstorage 中实现一个删除值,并展示如何添加一个 Getter 和 Setter 层,而不是,
localstorage.setItem(preview, true)
你可以写
config.preview = true
好的,开始了:
var PT=Storage.prototype
if (typeof PT._setItem >='u')
PT._setItem = PT.setItem;
PT.setItem = function(key, value)
{
if (typeof value >='u') //..undefined
this.removeItem(key)
else
this._setItem(key, JSON.stringify(value));
}
if (typeof PT._getItem >='u')
PT._getItem = PT.getItem;
PT.getItem = function(key)
{
var ItemData = this._getItem(key)
try
{
return JSON.parse(ItemData);
}
catch(e)
{
return ItemData;
}
}
// Aliases for localStorage.set/getItem
get = localStorage.getItem.bind(localStorage)
set = localStorage.setItem.bind(localStorage)
// Create ConfigWrapperObject
var config = {}
// Helper to create getter & setter
function configCreate(PropToAdd){
Object.defineProperty( config, PropToAdd, {
get: function () { return (get(PropToAdd) )},
set: function (val) { set(PropToAdd, val)}
})
}
//------------------------------
// Usage Part
// Create properties
configCreate('preview')
configCreate('notification')
//...
// Configuration Data transfer
// Set
config.preview = true
// Get
config.preview
// Delete
config.preview = undefined
好吧,您可以使用 .bind(...)
去除别名部分。但是,我只是把它放进去,因为知道这一点真的很好。我花了几个小时找出为什么一个简单的 get = localStorage.getItem;
不起作用。
循环引用
在这个答案中,我专注于具有循环引用的纯数据对象(没有函数等),并开发提到的想法 by maja and mathheadinclouds(我使用他的测试用例,我的代码要短几倍)。
实际上,我们可以使用带有适当 replacer 的 JSON.stringify - 如果源对象包含对某个对象的多重引用,或者包含循环引用,那么我们通过特殊的路径字符串(类似于 JSONPath)引用它。
// JSON.strigify 使用 circ ref function refReplacer() { let m = new Map(), v = new Map(), init = null; return function(field, value) { let p = m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field);让 isComplex = value === Object(value) if (isComplex) m.set(value, p);让 pp = v.get(值)||'';让路径 = p.replace(/undefined\.\.?/, '');让 val = pp ? `#REF:${pp[0] == '[' ? '$':'$.'}${pp}` : 值; !在里面 ? (init=value) : (val===init ? val="#REF:$" : 0); if(!pp && isComplex) v.set(value, path);返回值; } } // --------------- // 测试 // --------------- // 生成带有重复/循环引用的 obj let obj = { L: { L: { v: 'lorem' }, R: { v: 'ipsum' } }, R: { L: { v: 'dolor' }, R: { L: { v: 'sit' }, R: { v: 'amet' } } } } obj.RLuncle = obj.L; obj.RRuncle = obj.L; obj.RRLuncle = obj.RL; obj.RRRuncle = obj.RL; obj.LLuncle = obj.R; obj.LRuncle = obj.R;测试对象 = 对象;让 json = JSON.stringify(testObject, refReplacer(), 4); console.log("测试对象\n", testObject); console.log("带有 JSONpath 引用的 JSON\n", json);
使用类似 JSONpath 的引用解析此类 JSON 内容:
// 使用对象的 JSONpath 引用解析 JSON 内容 function parseRefJSON(json) { let objToPath = new Map();让 pathToObj = new Map();让 o = JSON.parse(json);让遍历=(父,字段)=> {让obj=父;让路径 = '#REF:$'; if (field !== undefined) { obj = parent[field]; path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field ? '.' + field : ''}`); } objToPath.set(obj, path); pathToObj.set(路径,obj);让 ref = pathToObj.get(obj);如果(参考)父母[字段] = 参考; for (let f in obj) if (obj === Object(obj)) traverse(obj, f); } 遍历(o);返回 o; } // --------------- // 测试 1 // --------------- let json = ` { "L": { " L“:{“v”:“lorem”,“叔叔”:{“L”:{“v”:“dolor”,“叔叔”:“#REF:$.L”},“R”:{“ L”:{“v”:“坐”,“叔叔”:“#REF:$.LLuncle.L”},“R”:{“v”:“amet”,“叔叔”:“#REF:$ .LLuncle.L" }, "uncle": "#REF:$.L" } } }, "R": { "v": "ipsum", "uncle": "#REF:$.LLuncle" } } , "R": "#REF:$.LLuncle" }`;让 testObject = parseRefJSON(json); console.log("测试对象\n", testObject); // --------------- // TEST 2 // --------------- console.log('Tests from mathheadinclouds answer: ') ;让恢复 = testObject; let obj = { // 原始对象 L: { L: { v: 'lorem' }, R: { v: 'ipsum' } }, R: { L: { v: 'dolor' }, R: { L: { v: '坐' }, R: { v: 'amet' } } } } obj.RLuncle = obj.L; obj.RRuncle = obj.L; obj.RRLuncle = obj.RL; obj.RRRuncle = obj.RL; obj.LLuncle = obj.R; obj.LRuncle = obj.R; [ obj.LLv === 恢复.LLv, obj.LRv === 恢复.LRv, obj.RLv === 恢复.RLv, obj.RRLv === 恢复.RRLv, obj.RRRv === 恢复.RRRv , obj.RLuncle === obj.L, obj.RRuncle === obj.L, obj.RRLuncle === obj.RL, obj.RRRuncle === obj.RL, obj.LLuncle === obj.R ,obj.LRuncle === obj.R,recovered.RLuncle ===recovered.L,recovered.RRuncle ===recovered.L,recovered.RRLuncle ===recovered.RL,recovered.RRRuncle ===recovered.RL , 恢复.LLuncle === 恢复.R, 恢复.LRuncle === 恢复.R ].forEach(x => console.log('test pass: ' + x));
要将生成的 JSON 内容加载/保存到存储中,请使用以下代码:
localStorage.myObject = JSON.stringify(testObject, refReplacer()); // Save
testObject = parseRefJSON(localStorage.myObject); // Load
我建议使用 Jackson-js。它是一个基于装饰器处理对象的序列化和反序列化同时保留其结构的库。
该库处理所有缺陷,例如循环引用、属性别名等。
使用 @JsonProperty() 和 @JsonClassType() 装饰器简单地描述你的类。
使用以下方法序列化您的对象:
const objectMapper = new ObjectMapper();
localstore.setItem(key, objectMapper.stringify<yourObjectType>(yourObject));
如需更详细的解释,请在此处查看我的答案:
Typescript objects serialization?
Jackson-js 教程在这里:
localStorage.setItem('user', JSON.stringify(user));
然后从存储中检索它并再次转换为对象:
var user = JSON.parse(localStorage.getItem('user'));
If we need to delete all entries of the store we can simply do:
localStorage.clear();
JSON.stringify()
将引用的对象扩展为我们字符串化对象中的完整“内容”(隐式字符串化)。请参阅:stackoverflow.com/a/12659424/2044940