【问题标题】:How to serialize typescript object to firebase?如何将打字稿对象序列化为firebase?
【发布时间】:2021-07-01 02:41:42
【问题描述】:

我有一个问题: 在我的 Angular 应用程序中,我使用了很多具有继承的类,但我只是注意到,在尝试将这些对象保存到 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 的对象,它们应该保持类(GeoPoint)而不是被转换。

【问题讨论】:

  • 您可以添加您的尝试和错误消息以帮助其他人复制问题吗?
  • @vitooh 我添加了更多信息:)
  • 为什么不使用 JSON.stringify 将 typescript 转换为简单的 json 并在读取时使用 copyInto 从 json 形成 typescript 对象?
  • 因为我没有一个级别的类,而是几个,并且某些字段是抽象对象的集合。但是,如果您知道如何使用 JSON.stringify 执行此操作,我会很高兴看到您如何进行?
  • JSON.stringify 将序列化深层对象。但问题是,你将如何反序列化那个东西,因为你不知道Itinerary 类中的正确类型抽象类型StepItinerary 类中的步骤数组变得异构,这会导致所有问题。

标签: angular typescript firebase google-cloud-firestore angularfire2


【解决方案1】:

我建议您在 Trip 类中创建 toJSONfromJSON,并按照 Ratul Sharker 的建议使用 JSON.stringify。这样做,您将能够控制对象的“复杂性”以及您想在 FB 消息中使用或不使用的内容。

More information here

更新

首先,我将在Step 类中创建一个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;
  }
}

然后在所有 Step 类中都会添加一个构造函数来确定其类型:

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 类中添加了将对象转换为纯 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

为避免在自定义类中使用type 属性,只需删除type 属性并为此替换Tripclass 中的getJSONObject 函数:

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 中替换它...

【讨论】:

  • 您好,感谢您的反馈。您将如何继续序列化具有Step 数组的Itinerary。 Step 作为一个抽象类,我需要知道哪个已经被序列化了。因为我不知道要打电话给哪个fromJSON?你能举个例子吗?喜欢 Itinerary 类的例子吗?
  • 感谢您的编辑,我明白您的意思。你认为我们可以避免在打字稿对象上使用这种“类型”吗?我猜它应该只在javascript版本上序列化?
  • 是的,我认为可以避免在getJSObject 中设置它,在steps 上迭代并在纯JS 返回的对象上设置type... 与重新创建时所做的相同。跨度>
  • 我回家后测试一下,谢谢:)
  • 我目前正在检查真正的实现,但是由于用一种方法(JSON.Stringify)将整个内容生成为 JSON,我不确定如何介入并添加信息(比如步骤类型)?有没有办法让这个 JSON.Stringify/parse 不做“深度”复制,然后在每个级别上都有“getJSObject”方法?我找不到如何不进行深层复制?
猜你喜欢
  • 1970-01-01
  • 2015-05-26
  • 2017-10-13
  • 1970-01-01
  • 1970-01-01
  • 2010-09-13
  • 2022-01-23
  • 2020-12-15
  • 2020-11-02
相关资源
最近更新 更多