如何将 typescript 对象序列化为 firebase?
How to serialize typescript object to firebase?
我有一个问题:
在我的 angular 应用程序中,我使用了很多具有继承性的 classes,但我只是注意到在尝试将这些对象保存到 firebase 时,我收到一条错误消息,提示我正在尝试保留一些海关对象,但不受支持。
这是错误:
ERROR FirebaseError: Function DocumentReference.set() called with invalid data (via `toFirestore()`). Unsupported field value: a custom object (found in field itinerary in document trips/iLK63UGEsYpZbn2NG2N7)
at new n (http://localhost:8100/vendor.js:39963:23)
at Gu (http://localhost:8100/vendor.js:53855:16)
at t.i_ (http://localhost:8100/vendor.js:53575:16)
at Mu (http://localhost:8100/vendor.js:53808:37)
at Uu (http://localhost:8100/vendor.js:53692:50)
at http://localhost:8100/vendor.js:53792:17
at _ (http://localhost:8100/vendor.js:40153:68)
at Cu (http://localhost:8100/vendor.js:53791:56)
at Ru (http://localhost:8100/vendor.js:53616:19)
at n.set (http://localhost:8100/vendor.js:55193:40)
defaultErrorLogger @ core.js:5980
那怎么序列化呢?
因为我有一些复杂的对象,它们会有一些很好的辅助方法,但我没有找到一种简单的方法来转换我的模型。
目前最小型号为:
export class Trip {
id: string;
owner: string;
name: string;
startDate: Date;
endDate: Date | undefined;
coverImageUrl: string;
itinerary: Itinerary;
constructor() {
this.itinerary = new Itinerary();
}
}
export class Itinerary {
stats: ItineraryStats;
steps: Step[];
constructor() {
this.steps = Step[0];
this.stats = new ItineraryStats();
}
}
export class ItineraryStats {
duration: number;
driveTime: number;
driveDistance: number;
averageDriveDistancePerDay(): number {
return this.driveDistance / this.duration;
}
averageDriveTimePerDay(): number {
return this.driveTime / this.duration;
}
}
export abstract class Step {
start: Date;
end: Date;
duration: number;
enforcedStartStop: boolean;
warning: string;
}
export abstract class StopStep extends Step {
id: string;
name: string;
description: string;
pointOfInterest: PointOfInterest | null;
address: string;
coordinates: firebase.firestore.GeoPoint;
}
export class ActivityStep extends StopStep {
duration: number;
constructor() {
super();
this.id = Guid.newGuid();
}
}
export class NightStep extends StopStep {
nightNumber: number;
numberOfNight: number;
}
export class PointOfInterest {
type: PointOfInterest;
}
export class TravelStep extends Step {
Mode: TravelMode;
PolyLines: string;
}
我正在尝试在此处保存一次旅行。
我正在使用 NGXS + Angular fire,但我猜错误与仅 angularfire 相同。
我尝试使用将 typescript 转换为 json 的库 (classToPlain),但它不起作用,因为某些对象是 Firebase 的对象,应该保留 class(GeoPoint) 并且不被转换。
我建议您在 Trip
class 中创建 toJSON
和 fromJSON
,并按照 Ratul Sharker 的建议使用 JSON.stringify
。这样做,您将能够控制您对象的 'complexity' 以及您想在 FB 消息中使用或不使用的内容。
更新
首先,我会在 Step
class 中创建一个 type
属性 和一个构造函数来强制所有后代都确定其类型。因此,我们将拥有重建对象所需的所有信息,还将创建一个静态函数 - 让我们称它为 CreateFromJS
以从纯 JS 对象重建一个步骤:
export enum StepTypes {
Unkown=0,
Stop=1,
Activity=2,
Night=3,
Travel=4
}
export abstract class Step {
start: Date;
end: Date;
duration: number;
enforcedStartStop: boolean;
warning: string;
type: StepTypes;
constructor(type:StepTypes) {
this.type=type;
}
static createFromJS(obj: any) {
let step=null;
switch(obj.type) {
case StepTypes.Unkown:
case StepTypes.Stop:
// ERROR!
break;
case StepTypes.Activity:
step=new ActivityStep();
break;
case StepTypes.Night:
step=new NightStep();
break;
case StepTypes.Travel:
step=new TravelStep();
break;
}
if(step) {
Object.assign(step, obj);
} else {
step=obj;
}
return step;
}
}
然后在所有步骤classes中将添加一个构造函数来确定其类型:
export abstract class StopStep extends Step {
id: string;
name: string;
description: string;
pointOfInterest: PointOfInterest | null;
address: string;
coordinates: GeoPoint;
constructor() {
super(StepTypes.Stop);
}
}
export class ActivityStep extends StopStep {
duration: number;
constructor() {
super();
this.type=StepTypes.Activity;
this.id = 'Guid.newGuid()';
}
}
export class NightStep extends StopStep {
nightNumber: number;
numberOfNight: number;
constructor() {
super();
this.type=StepTypes.Night;
}
}
export class TravelStep extends Step {
Mode: TravelMode;
PolyLines: string;
constructor() {
super(StepTypes.Travel);
}
}
最后在 Trip
class 中将添加将对象转换为纯 JS 的函数以及从纯 JS 恢复对象的函数:
export class Trip {
id: string;
owner: string;
name: string;
startDate: Date;
endDate: Date | undefined;
coverImageUrl: string;
itinerary: Itinerary;
constructor() {
this.itinerary = new Itinerary();
}
getJSObject() {
return JSON.parse(JSON.stringify(this));
}
static createFromJS(fb:any):Trip {
const trip:Trip=new Trip();
Object.assign(trip, fb);
for(let i=0;i<trip.itinerary.steps.length;i++) {
trip.itinerary.steps[i]=Step.createFromJS(trip.itinerary.steps[i]);
}
return trip;
}
}
由于 Firebase 不允许自定义对象,我们必须在存储它们之前将其转换为纯 JS,因此当您将行程存储到 firebase 时,您必须调用 getJSObject
并存储返回值。
当您从存储中恢复对象时,您只需要使用静态方法创建它 createFromJS
即可恢复自定义类型。
要测试它只需要创建一个行程并添加一些步骤...并尝试我们的新功能,我们可以在控制台中检查第一个行程有类型然后 jst
没有类型(应该在 Firebase 上工作),最后再次从纯 JS 恢复它的类型...所以:
const trip=new Trip();
const actStep=new ActivityStep();
const nightStep=new NightStep();
const travelStep=new TravelStep();
Object.assign(trip, {id:'id', coverImageUrl:'url', name:'name', owner: 'owner', startDate:new Date(2021, 5, 10), endDate: new Date(2021, 5, 12)});
Object.assign(trip.itinerary.stats, {driveDistance:100, driveTime: 10, duration: 2});
actStep.address='address';
actStep.description='Desc';
actStep.duration=50;
actStep.enforcedStartStop=false;
actStep.pointOfInterest=new PointOfInterest;
trip.itinerary.steps.push(actStep, nightStep, travelStep);
const jst=trip.getJSObject();
const parsed=Trip.createFromJS(jst);
console.log(trip);
console.log(jst);
console.log(parsed);
更新 2
为了避免在自定义 class 中使用 type
属性 只需删除 type
属性 并替换 [=17 中的 getJSONObject
函数=]class为此:
getJSObject() {
const json=JSON.parse(JSON.stringify(this));
let type;
for(let i=0;i<this.itinerary.steps.length;i++) {
switch(this.itinerary.steps[i].constructor.name) {
case 'ActivityStep':
type=StepTypes.Activity;
break;
case 'NightStep':
type=StepTypes.Night;
break;
case 'TravelStep':
type=StepTypes.Travel;
break;
default:
type=0;
}
// Set type in pure JS...
json.itinerary.steps[i].type=type;
}
return json;
}
因为我们有相同的对象...行程的索引相同所以我们从对象中获取类型并将其替换为纯 JS 类型...
我有一个问题: 在我的 angular 应用程序中,我使用了很多具有继承性的 classes,但我只是注意到在尝试将这些对象保存到 firebase 时,我收到一条错误消息,提示我正在尝试保留一些海关对象,但不受支持。
这是错误:
ERROR FirebaseError: Function DocumentReference.set() called with invalid data (via `toFirestore()`). Unsupported field value: a custom object (found in field itinerary in document trips/iLK63UGEsYpZbn2NG2N7)
at new n (http://localhost:8100/vendor.js:39963:23)
at Gu (http://localhost:8100/vendor.js:53855:16)
at t.i_ (http://localhost:8100/vendor.js:53575:16)
at Mu (http://localhost:8100/vendor.js:53808:37)
at Uu (http://localhost:8100/vendor.js:53692:50)
at http://localhost:8100/vendor.js:53792:17
at _ (http://localhost:8100/vendor.js:40153:68)
at Cu (http://localhost:8100/vendor.js:53791:56)
at Ru (http://localhost:8100/vendor.js:53616:19)
at n.set (http://localhost:8100/vendor.js:55193:40)
defaultErrorLogger @ core.js:5980
那怎么序列化呢?
因为我有一些复杂的对象,它们会有一些很好的辅助方法,但我没有找到一种简单的方法来转换我的模型。
目前最小型号为:
export class Trip {
id: string;
owner: string;
name: string;
startDate: Date;
endDate: Date | undefined;
coverImageUrl: string;
itinerary: Itinerary;
constructor() {
this.itinerary = new Itinerary();
}
}
export class Itinerary {
stats: ItineraryStats;
steps: Step[];
constructor() {
this.steps = Step[0];
this.stats = new ItineraryStats();
}
}
export class ItineraryStats {
duration: number;
driveTime: number;
driveDistance: number;
averageDriveDistancePerDay(): number {
return this.driveDistance / this.duration;
}
averageDriveTimePerDay(): number {
return this.driveTime / this.duration;
}
}
export abstract class Step {
start: Date;
end: Date;
duration: number;
enforcedStartStop: boolean;
warning: string;
}
export abstract class StopStep extends Step {
id: string;
name: string;
description: string;
pointOfInterest: PointOfInterest | null;
address: string;
coordinates: firebase.firestore.GeoPoint;
}
export class ActivityStep extends StopStep {
duration: number;
constructor() {
super();
this.id = Guid.newGuid();
}
}
export class NightStep extends StopStep {
nightNumber: number;
numberOfNight: number;
}
export class PointOfInterest {
type: PointOfInterest;
}
export class TravelStep extends Step {
Mode: TravelMode;
PolyLines: string;
}
我正在尝试在此处保存一次旅行。
我正在使用 NGXS + Angular fire,但我猜错误与仅 angularfire 相同。
我尝试使用将 typescript 转换为 json 的库 (classToPlain),但它不起作用,因为某些对象是 Firebase 的对象,应该保留 class(GeoPoint) 并且不被转换。
我建议您在 Trip
class 中创建 toJSON
和 fromJSON
,并按照 Ratul Sharker 的建议使用 JSON.stringify
。这样做,您将能够控制您对象的 'complexity' 以及您想在 FB 消息中使用或不使用的内容。
更新
首先,我会在 Step
class 中创建一个 type
属性 和一个构造函数来强制所有后代都确定其类型。因此,我们将拥有重建对象所需的所有信息,还将创建一个静态函数 - 让我们称它为 CreateFromJS
以从纯 JS 对象重建一个步骤:
export enum StepTypes {
Unkown=0,
Stop=1,
Activity=2,
Night=3,
Travel=4
}
export abstract class Step {
start: Date;
end: Date;
duration: number;
enforcedStartStop: boolean;
warning: string;
type: StepTypes;
constructor(type:StepTypes) {
this.type=type;
}
static createFromJS(obj: any) {
let step=null;
switch(obj.type) {
case StepTypes.Unkown:
case StepTypes.Stop:
// ERROR!
break;
case StepTypes.Activity:
step=new ActivityStep();
break;
case StepTypes.Night:
step=new NightStep();
break;
case StepTypes.Travel:
step=new TravelStep();
break;
}
if(step) {
Object.assign(step, obj);
} else {
step=obj;
}
return step;
}
}
然后在所有步骤classes中将添加一个构造函数来确定其类型:
export abstract class StopStep extends Step {
id: string;
name: string;
description: string;
pointOfInterest: PointOfInterest | null;
address: string;
coordinates: GeoPoint;
constructor() {
super(StepTypes.Stop);
}
}
export class ActivityStep extends StopStep {
duration: number;
constructor() {
super();
this.type=StepTypes.Activity;
this.id = 'Guid.newGuid()';
}
}
export class NightStep extends StopStep {
nightNumber: number;
numberOfNight: number;
constructor() {
super();
this.type=StepTypes.Night;
}
}
export class TravelStep extends Step {
Mode: TravelMode;
PolyLines: string;
constructor() {
super(StepTypes.Travel);
}
}
最后在 Trip
class 中将添加将对象转换为纯 JS 的函数以及从纯 JS 恢复对象的函数:
export class Trip {
id: string;
owner: string;
name: string;
startDate: Date;
endDate: Date | undefined;
coverImageUrl: string;
itinerary: Itinerary;
constructor() {
this.itinerary = new Itinerary();
}
getJSObject() {
return JSON.parse(JSON.stringify(this));
}
static createFromJS(fb:any):Trip {
const trip:Trip=new Trip();
Object.assign(trip, fb);
for(let i=0;i<trip.itinerary.steps.length;i++) {
trip.itinerary.steps[i]=Step.createFromJS(trip.itinerary.steps[i]);
}
return trip;
}
}
由于 Firebase 不允许自定义对象,我们必须在存储它们之前将其转换为纯 JS,因此当您将行程存储到 firebase 时,您必须调用 getJSObject
并存储返回值。
当您从存储中恢复对象时,您只需要使用静态方法创建它 createFromJS
即可恢复自定义类型。
要测试它只需要创建一个行程并添加一些步骤...并尝试我们的新功能,我们可以在控制台中检查第一个行程有类型然后 jst
没有类型(应该在 Firebase 上工作),最后再次从纯 JS 恢复它的类型...所以:
const trip=new Trip();
const actStep=new ActivityStep();
const nightStep=new NightStep();
const travelStep=new TravelStep();
Object.assign(trip, {id:'id', coverImageUrl:'url', name:'name', owner: 'owner', startDate:new Date(2021, 5, 10), endDate: new Date(2021, 5, 12)});
Object.assign(trip.itinerary.stats, {driveDistance:100, driveTime: 10, duration: 2});
actStep.address='address';
actStep.description='Desc';
actStep.duration=50;
actStep.enforcedStartStop=false;
actStep.pointOfInterest=new PointOfInterest;
trip.itinerary.steps.push(actStep, nightStep, travelStep);
const jst=trip.getJSObject();
const parsed=Trip.createFromJS(jst);
console.log(trip);
console.log(jst);
console.log(parsed);
更新 2
为了避免在自定义 class 中使用 type
属性 只需删除 type
属性 并替换 [=17 中的 getJSONObject
函数=]class为此:
getJSObject() {
const json=JSON.parse(JSON.stringify(this));
let type;
for(let i=0;i<this.itinerary.steps.length;i++) {
switch(this.itinerary.steps[i].constructor.name) {
case 'ActivityStep':
type=StepTypes.Activity;
break;
case 'NightStep':
type=StepTypes.Night;
break;
case 'TravelStep':
type=StepTypes.Travel;
break;
default:
type=0;
}
// Set type in pure JS...
json.itinerary.steps[i].type=type;
}
return json;
}
因为我们有相同的对象...行程的索引相同所以我们从对象中获取类型并将其替换为纯 JS 类型...