如何将 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 中创建 toJSONfromJSON,并按照 Ratul Sharker 的建议使用 JSON.stringify。这样做,您将能够控制您对象的 'complexity' 以及您想在 FB 消息中使用或不使用的内容。

More information here

更新

首先,我会在 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 类型...