提问人:Kristopher Johnson 提问时间:1/6/2010 最后编辑:Samuel LiewKristopher Johnson 更新时间:10/12/2023 访问量:1437844
如何在 HTML5 localStorage/sessionStorage 中存储对象
How to store objects in HTML5 localStorage/sessionStorage
问:
我想在 HTML5 中存储一个 JavaScript 对象,但我的对象显然正在转换为字符串。localStorage
我可以使用 存储和检索原始 JavaScript 类型和数组,但对象似乎不起作用。他们应该吗?localStorage
这是我的代码:
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 存储规范的误解,而不是特定于浏览器的错误或限制。
我试图理解 2 通用基础架构中描述的结构化克隆算法。我不完全理解它在说什么,但也许我的问题与我的对象的属性不可枚举(???)有关。
有没有简单的解决方法?
更新:W3C 最终改变了他们对结构化克隆规范的看法,并决定更改规范以匹配实现。请参阅 12111 – 存储对象 getItem(key) 方法与实现行为不匹配的规范。所以这个问题不再是 100% 有效,但答案仍然可能引起人们的兴趣。
答:
再次查看Apple,Mozilla和Mozilla文档,该功能似乎仅限于处理字符串键/值对。
解决方法是在存储对象之前对其进行字符串化,然后在检索对象时对其进行解析:
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));
评论
JSON.stringify()
您可能会发现使用以下方便的方法扩展 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()
setObject
对变体的微小改进:
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);
}
由于短路评估,如果不在存储中,将立即返回。如果 is(空字符串; 无法处理)。getObject()
null
key
SyntaxError
value
""
JSON.parse()
评论
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
String(null) === "null"
""
false
JSON.parse("")
SyntaxError
为 Storage 对象创建外观是一个很棒的解决方案。这样,您就可以实现自己的方法。对于我的 API,我为 localStorage 创建了一个外观,然后在设置和获取时检查它是否是对象。get
set
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'});
从理论上讲,可以使用以下函数存储对象:
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;
}
但是,函数序列化/反序列化是不可靠的,因为它依赖于实现。
评论
c.f[k] = escape(a[k]);
c.f[k] = encodeURIComponent(a[k]);
eval('b.' + k + ' = ' + unescape(data.f[k]));
b[k] = eval("(" + decodeURIComponent(data.f[k]) + ")");
eval()
SyntaxError
typeof
typeof(a[k])
typeof a[k]
for
in
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 () {}
有一个很棒的库,它包装了许多解决方案,因此它甚至支持称为 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
评论
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有关。
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()
通常,不应在 JavaScript 代码中使用。eval()
私人会员
用于存储对象的问题在于,此函数不能序列化私有成员。JSON.stringify()
此问题可以通过覆盖方法(在 Web 存储中存储数据时隐式调用)来解决:.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”。JSON.stringify()
TypeError
如果应支持存储循环引用,则可以使用 的第二个参数: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 上已经有一些问题在处理这个问题:字符串化(转换为 JSON)一个具有循环引用的 JavaScript 对象
评论
localStorage.num
num = +localStorage.num
以下是 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;
评论
建议对此处讨论的许多功能使用抽象库,以及更好的兼容性。有很多选择:
- jStorage 或 simpleStorage ←我的偏好
- 本地草料
- Alekseykulikov/存储
- 草坪椅
- Store.js←另一个不错的选择
- 我的天啊
- localDataStorage
评论
我做了一个不会破坏现有 Storage 对象的东西,但会创建一个包装器,以便您可以做您想做的事。结果是一个普通的对象,没有方法,像任何对象一样具有访问权限。
如果你想让 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'});
被跟踪对象中的每个新对象都将被自动跟踪。
非常大的缺点:它取决于它对浏览器的支持非常有限。而且看起来它不会很快出现在Firefox或Edge上。Object.observe()
评论
Object.observe
现在,在所有主流浏览器中均已弃用。
另一种选择是使用现有的插件。
例如,persisto 是一个开源项目,它为 localStorage/sessionStorage 提供了一个简单的接口,并自动保留了表单字段(输入、单选按钮和复选框)。
(免责声明:我是作者。
评论
localStroage
var lsh = new localStorageHelper(); lsh.setItem('bob', 'bill');
可以使用 ejson 将对象存储为字符串。
EJSON 是 JSON 的扩展,可支持更多类型。它支持所有 JSON 安全类型,以及:
- 日期 (JavaScript
Date
)- 二进制(JavaScript 或 EJSON.newBinary 的结果
Uint8Array
)- 用户定义的类型(请参阅 EJSON.addType。例如,Mongo.ObjectID 就是这样实现的。
所有 EJSON 序列化也是有效的 JSON。例如,具有日期和二进制缓冲区的对象将在 EJSON 中序列化为:
{ "d": {"$date": 1358205756553}, "b": {"$binary": "c3VyZS4="} }
这是我使用 ejson 的 localStorage 包装器
https://github.com/UziTech/storage.js
我在包装器中添加了一些类型,包括正则表达式和函数
我用 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
localDataStorage 是 HTML5 localStorage API 的同步 JavaScript 接口,它——
- 使用数据“类型”(如 Array、BigInt、Boolean、Date、Float、Integer、Object 和 String)透明地设置/获取键值;
- 提供轻量级数据混淆;
- 智能压缩字符串(节省存储空间);
- 有助于稳健的查找,包括按键(名称)查询、按(键)值查询和按存在查询(布尔检查);
- 通过在键前加上前缀,在同一域中强制实施分段共享存储;
- 允许您在触发 localStorage 更改事件的同一页面/选项卡上响应这些事件;
- 在源中广播更改事件,以方便其他窗口/选项卡;
- 允许您使用专用的阵列键轻松处理阵列;和
- 提供内存密钥(可备份到磁盘)以实现尽可能快的读取时间。
[免责声明]我是实用程序的作者 [/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
正如你所看到的,原始值是被尊重的。
评论
For TypeScript users willing to set and get typed properties:
/**
* 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");
I found a way to make it work with objects that have cyclic references.
Let's make an object with cyclic references.
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;
We can't do here, because of the circular references.JSON.stringify
LOCALSTORAGE.CYCLICJSON
has and just like normal , but works with objects with circular references. ("Works" meaning parse(stringify(obj)) and obj are deep equal AND have identical sets of 'inner equalities').stringify
.parse
JSON
But we can just use the shortcuts:
LOCALSTORAGE.setObject('latinUncles', obj)
recovered = LOCALSTORAGE.getObject('latinUncles')
Then, will be "the same" to obj, in the following sense:recovered
[
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
]
Here is the implementation of 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<ignore.length; i++){
if (item instanceof ignore[i]) { return true; }
}
return false;
} else {
return true;
}
}
function infant(value){
return Array.isArray(value) ? [] : {};
}
function decycleIntoForest(object, replacer) {
if (typeof replacer !== 'function'){
replacer = function(x){ return x; }
}
object = replacer(object);
if (primitive(object)) return object;
var objects = [object];
var forest = [infant(object)];
var bucket = new WeakMap(); // bucket = inverse of objects
bucket.set(object, 0);
function addToBucket(obj){
var result = objects.length;
objects.push(obj);
bucket.set(obj, result);
return result;
}
function isInBucket(obj){ return bucket.has(obj); }
function processNode(source, target){
Object.keys(source).forEach(function(key){
var value = replacer(source[key]);
if (primitive(value)){
target[key] = {value: value};
} else {
var ptr;
if (isInBucket(value)){
ptr = bucket.get(value);
} else {
ptr = addToBucket(value);
var newTree = infant(value);
forest.push(newTree);
processNode(value, newTree);
}
target[key] = {pointer: ptr};
}
});
}
processNode(object, forest[0]);
return forest;
};
function deForestIntoCycle(forest) {
var objects = [];
var objectRequested = [];
var todo = [];
function processTree(idx) {
if (idx in objects) return objects[idx];
if (objectRequested[idx]) return null;
objectRequested[idx] = true;
var tree = forest[idx];
var node = Array.isArray(tree) ? [] : {};
for (var key in tree) {
var o = tree[key];
if ('pointer' in o) {
var ptr = o.pointer;
var value = processTree(ptr);
if (value === null) {
todo.push({
node: node,
key: key,
idx: ptr
});
} else {
node[key] = value;
}
} else {
if ('value' in o) {
node[key] = o.value;
} else {
throw new Error('unexpected')
}
}
}
objects[idx] = node;
return node;
}
var result = processTree(0);
for (var i = 0; i < todo.length; i++) {
var item = todo[i];
item.node[item.key] = objects[item.idx];
}
return result;
};
var console = {
log: function(x){
var the = document.getElementById('the');
the.textContent = the.textContent + '\n' + x;
},
delimiter: function(){
var the = document.getElementById('the');
the.textContent = the.textContent +
'\n*******************************************';
}
}
function logCyclicObjectToConsole(root) {
var cycleFree = decycleIntoForest(root);
var shown = cycleFree.map(function(tree, idx) {
return false;
});
var indentIncrement = 4;
function showItem(nodeSlot, indent, label) {
var leadingSpaces = ' '.repeat(indent);
var leadingSpacesPlus = ' '.repeat(indent + indentIncrement);
if (shown[nodeSlot]) {
console.log(leadingSpaces + label + ' ... see above (object #' + nodeSlot + ')');
} else {
console.log(leadingSpaces + label + ' object#' + nodeSlot);
var tree = cycleFree[nodeSlot];
shown[nodeSlot] = true;
Object.keys(tree).forEach(function(key) {
var entry = tree[key];
if ('value' in entry) {
console.log(leadingSpacesPlus + key + ": " + entry.value);
} else {
if ('pointer' in entry) {
showItem(entry.pointer, indent + indentIncrement, key);
}
}
});
}
}
console.delimiter();
showItem(0, 0, 'root');
};
function stringify(obj){
return JSON.stringify(decycleIntoForest(obj));
}
function parse(str){
return deForestIntoCycle(JSON.parse(str));
}
var CYCLICJSON = {
decycleIntoForest: decycleIntoForest,
deForestIntoCycle : deForestIntoCycle,
logCyclicObjectToConsole: logCyclicObjectToConsole,
stringify : stringify,
parse : parse
}
function setObject(name, object){
var str = stringify(object);
localStorage.setItem(name, str);
}
function getObject(name){
var str = localStorage.getItem(name);
if (str===null) return null;
return parse(str);
}
return {
CYCLICJSON : CYCLICJSON,
setObject : setObject,
getObject : getObject
}
})();
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;
// LOCALSTORAGE.setObject('latinUncles', obj)
// recovered = LOCALSTORAGE.getObject('latinUncles')
// localStorage not available inside fiddle ):
LOCALSTORAGE.CYCLICJSON.logCyclicObjectToConsole(obj)
putIntoLS = LOCALSTORAGE.CYCLICJSON.stringify(obj);
recovered = LOCALSTORAGE.CYCLICJSON.parse(putIntoLS);
LOCALSTORAGE.CYCLICJSON.logCyclicObjectToConsole(recovered);
var the = document.getElementById('the');
the.textContent = the.textContent + '\n\n' +
JSON.stringify(
[
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
]
)
<pre id='the'></pre>
You cannot store a key value without a string format.
LocalStorage only supports string formats for keys/values.
That is why you should convert your data to string whatever it is an array or object.
To store data in localStorage, first of all stringify it using the JSON.stringify() method.
var myObj = [{name:"test", time:"Date 2017-02-03T08:38:04.449Z"}];
localStorage.setItem('item', JSON.stringify(myObj));
Then when you want to retrieve data, you need to parse the string to object again.
var getObj = JSON.parse(localStorage.getItem('item'));
评论
localStorage.setItem('user', JSON.stringify(user));
Then to retrieve it from the store and convert to an object again:
var user = JSON.parse(localStorage.getItem('user'));
If we need to delete all entries of the store we can simply do:
localStorage.clear();
评论
Circular References
In this answer I focus on data-only objects (without functions, etc.) with circular references and develop ideas mentioned by maja and mathheadinclouds (I use his test case and my code is several times shorter).
Actually, we can use JSON.stringify with a proper replacer - if the source object contains multi-references to some object, or contains circular references then we reference it by special path-string (similar to JSONPath).
// JSON.strigify replacer for objects with 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);
let isComplex = value === Object(value)
if (isComplex) m.set(value, p);
let pp = v.get(value)||'';
let path = p.replace(/undefined\.\.?/, '');
let val = pp ? `#REF:${pp[0] == '[' ? '$':'$.'}${pp}` : value;
!init ? (init=value) : (val===init ? val="#REF:$" : 0);
if(!pp && isComplex) v.set(value, path);
return val;
}
}
// ---------------
// TEST
// ---------------
// Generate obj with duplicate/circular references
let 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;
testObject = obj;
let json = JSON.stringify(testObject, refReplacer(), 4);
console.log("Test Object\n", testObject);
console.log("JSON with JSONpath references\n", json);
使用类似 JSONpath 的引用解析此类 JSON 内容:
// Parse JSON content with JSONpath references to object
function parseRefJSON(json) {
let objToPath = new Map();
let pathToObj = new Map();
let o = JSON.parse(json);
let traverse = (parent, field) => {
let obj = parent;
let path = '#REF:$';
if (field !== undefined) {
obj = parent[field];
path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field ? '.' + field : ''}`);
}
objToPath.set(obj, path);
pathToObj.set(path, obj);
let ref = pathToObj.get(obj);
if (ref) parent[field] = ref;
for (let f in obj) if (obj === Object(obj)) traverse(obj, f);
}
traverse(o);
return o;
}
// ---------------
// TEST 1
// ---------------
let json = `
{
"L": {
"L": {
"v": "lorem",
"uncle": {
"L": {
"v": "dolor",
"uncle": "#REF:$.L"
},
"R": {
"L": {
"v": "sit",
"uncle": "#REF:$.L.L.uncle.L"
},
"R": {
"v": "amet",
"uncle": "#REF:$.L.L.uncle.L"
},
"uncle": "#REF:$.L"
}
}
},
"R": {
"v": "ipsum",
"uncle": "#REF:$.L.L.uncle"
}
},
"R": "#REF:$.L.L.uncle"
}`;
let testObject = parseRefJSON(json);
console.log("Test Object\n", testObject);
// ---------------
// TEST 2
// ---------------
console.log('Tests from mathheadinclouds answer: ');
let recovered = testObject;
let obj = { // Original object
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;
[
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
].forEach(x => console.log('test pass: ' + x));
若要将生成的 JSON 内容加载/保存到存储中,请使用以下代码:
localStorage.myObject = JSON.stringify(testObject, refReplacer()); // Save
testObject = parseRefJSON(localStorage.myObject); // Load
评论
我建议使用 Jackson-js。它是一个库,用于处理对象的序列化和反序列化,同时保留其结构,基于装饰器。
The library handles all the pitfalls such as cyclic reference, attributes aliasing, etc.
Simply describe your class using the @JsonProperty() and @JsonClassType() decorators.
Serialize your object using:
const objectMapper = new ObjectMapper();
localstore.setItem(key, objectMapper.stringify<yourObjectType>(yourObject));
For slightly more detailed explanation, check my answer here:
Typescript objects serialization?
And the Jackson-js tutorial here:
评论
This question has been answered sufficiently from the JavaScript-only perspective, and others have already noted that both and have no concept of objects—they handle strings and strings only. This answer provides a TypeScript-friendly solution that incorporates what others have suggested in JavaScript-only solutions.localStorage.getItem
localStorage.setItem
TypeScript 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;
}
}
Usage
localStorage.setObject('ages', [23, 18, 33, 22, 58]);
localStorage.getObject('ages');
Explanation
We declare both and functions on the prototype— is an instance of this type. There's nothing special we really need to note besides the null handling in . Since can return , we must exit early since calling on a value will throw a runtime exception.setObject
getObject
Storage
localStorage
getObject
getItem
null
JSON.parse
null
After declaring the functions on the prototype, we include their type definitions on the type in the global namespace.Storage
Storage
Note: If we defined these functions with arrow functions, we'd need to assume that the storage object we're calling is always , which might not be true. For instance, the above code will add and support to as well.localStorage
setObject
getObject
sessionStorage
评论
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
评论