使用云功能时,来自 Firestore 的时间戳会转换为地图

Posted

技术标签:

【中文标题】使用云功能时,来自 Firestore 的时间戳会转换为地图【英文标题】:Timestamp from firestore gets converted to a Map when using cloud function 【发布时间】:2019-10-08 06:05:03 【问题描述】:

所以我在 Cloud Firestore 中有一个 Timestamp。我正在使用云功能从 firestore 检索数据以进行颤动。但是JSON时间戳格式化为映射,因此我无法将其用作时间戳。如何再次将其转换为时间戳? 这就是我将时间戳添加到 Firestore 的方式。

var reference = Firestore.instance.collection('posts');
      reference.add(
        'postTitle': this.title,
        'timestamp': DateTime.now(),
        'likes': ,
        'ownerId': userId,
      )

要检索数据,代码如下:

 factory Post.fromJSON(Map data)
    return Post(
      timestamp: data['timestamp'],
    );
  
List<Post> _generateFeed(List<Map<String, dynamic>> feedData) 
    List<Post> listOfPosts = [];

    for (var postData in feedData) 
      listOfPosts.add(Post.fromJSON(postData));
    

    return listOfPosts;
  

但这会返回错误。

I/flutter (17271): The following assertion was thrown building FutureBuilder<DocumentSnapshot>(dirty, state:
I/flutter (17271): _FutureBuilderState<DocumentSnapshot>#1536b):
I/flutter (17271): type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Timestamp'

这是我的云功能。getFeed.ts

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

export const getFeedModule = function(req, res)
    const uid = String(req.query.uid);

    async function compileFeedPost()
        const following = await getFollowing(uid, res)as any;

        let listOfPosts = await getAllPosts(following, res);

        listOfPosts = [].concat.apply([], listOfPosts);

        res.send(listOfPosts);
    

    compileFeedPost().then().catch();


async function getAllPosts(following, res) 
    let listOfPosts = [];

    for (let user in following)
        listOfPosts.push( await getUserPosts(following[user], res));
    
    return listOfPosts;


function getUserPosts(userId, res)
    const posts = admin.firestore().collection("posts").where("ownerId", "==", userId).orderBy("timestamp")

    return posts.get()
    .then(function(querySnapshot)
        let listOfPosts = [];

        querySnapshot.forEach(function(doc)
            listOfPosts.push(doc.data());
        );

        return listOfPosts;
    )


function getFollowing(uid, res)
    const doc = admin.firestore().doc(`user/$uid`)
    return doc.get().then(snapshot => 
        const followings = snapshot.data().followings;

        let following_list = [];

        for (const following in followings)
            if (followings[following] === true)
                following_list.push(following);
            
        
        return following_list;
    ).catch(error => 
        res.status(500).send(error)
    )

云功能index.ts

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import  getFeedModule  from "./getFeed"
admin.initializeApp();

export const getFeed = functions.https.onRequest((req, res) => 
    getFeedModule(req, res);
)


由此调用

_getFeed() async 
    print("Starting getFeed");
    FirebaseUser user = await FirebaseAuth.instance.currentUser();

    SharedPreferences prefs = await SharedPreferences.getInstance();

    String userId = user.uid;
    var url =
        'https://us-central1-jaluk-quiz.cloudfunctions.net/getFeed?uid=' + userId;
    var httpClient = HttpClient();

    List<QuizViewer>listOfPosts;
    String result;
    try 
      var request = await httpClient.getUrl(Uri.parse(url));
      var response = await request.close(); 
      if (response.statusCode == HttpStatus.ok) 
        String json = await response.transform(utf8.decoder).join();
        prefs.setString("feed", json);
        List<Map<String, dynamic>> data =
            jsonDecode(json).cast<Map<String, dynamic>>();
        listOfPosts = _generateFeed(data);
        result = "Success in http request for feed";
       else 
        result =
            'Error getting a feed: Http status $response.statusCode | userId $userId';
      
     catch (exception) 
      result = 'Failed invoking the getFeed function. Exception: $exception';
    
    print(result);

    setState(() 
      feedData = listOfPosts;
    );
  

【问题讨论】:

你可能做错了什么。请编辑问题以显示所有未按您期望的方式工作的相关代码,包括填充数据库和将其读回。请验证您是否实际存储了时间戳类型的对象,而不是其他内容。 @DougStevenson 我已经用一些代码更新了我的问题。我是 Flutter 和编码的新手,所以我不知道该放哪一行代码。我的整个代码很长。 您提到了云功能。这是怎么回事?如果不涉及,请通过删除它来简化您的问题。 @DougStevenson 好的,我刚刚添加了云功能代码。 你的 Flutter 代码中涉及的函数是怎样的?它看起来与应用程序的执行方式无关,因为我们看不到它是如何被调用的,或者它究竟生成了什么。 【参考方案1】:

确实,在使用云函数时,时间戳会以普通 Map 的形式返回。但如果您使用 Firebase SDK,它会返回 Timestamp 对象。 我使用以下函数来处理这两种情况:

/// https://***.com/a/57865272/1321917
DateTime dateTimeFromTimestamp(dynamic val) 
  Timestamp timestamp;
  if (val is Timestamp) 
    timestamp = val;
   else if (val is Map) 
    timestamp = Timestamp(val['_seconds'], val['_nanoseconds']);
  
  if (timestamp != null) 
    return timestamp.toDate();
   else 
    print('Unable to parse Timestamp from $val');
    return null;
  

json_annotation lib 完美配合:

  @JsonKey(
      fromJson: dateTimeFromTimestamp,
      toJson: dateTimeToTimestamp,
      nullable: true)
  final DateTime subscriptionExpiryDate;

【讨论】:

【参考方案2】:

如果您正在处理已序列化为具有秒和纳秒组件的对象的时间戳,您可以使用这些组件创建一个新的具有new Timestamp(seconds, nanoseconds) 的Timestamp 对象。

【讨论】:

哇!那是天才。感谢大师的帮助!但是 JSON 以这种格式给我 _seconds: 1552543554634, _nanoseconds: 88000000。如何将其放入时间戳(秒,纳秒)? 我实际上不做飞镖编程。您必须弄清楚如何从 Cloud Functions 响应中获取这些值。如果您已经在访问响应的其他部分,这应该很简单。 哦,好的,没问题!你给我指路了。我会尝试把它放在时间戳中。再次感谢! @DougStevenson 无论如何要避免来自 cloudfunction 的这种序列化?每次我从 cloudfunction 返回带有时间戳的对象时,解析每个 JSON TIMESTAMP 并不方便【参考方案3】:

您可以像这样使用 convert 来接收 DateTime:

class TimestampConverter implements JsonConverter<DateTime, dynamic> 
  const TimestampConverter();

  @override
  DateTime fromJson(dynamic data) 
    Timestamp timestamp;
    if (data is Timestamp) 
      timestamp = data;
     else if (data is Map) 
      timestamp = Timestamp(data['_seconds'], data['_nanoseconds']);
    
    return timestamp?.toDate();
  

  @override
  Map<String, dynamic> toJson(DateTime dateTime) 
    final timestamp = Timestamp.fromDate(dateTime);
    return 
      '_seconds': timestamp.seconds,
      '_nanoseconds': timestamp.nanoseconds,
    ;
  

然后像这样标记你的模型字段:

@TimestampConverter() DateTime createdAt

【讨论】:

【参考方案4】:

Andrey 的回答非常好。这是一个 JS/Typescript 改编,封装在一个类中:

import app from 'firebase/app'
import 'firebase/firestore'

import TimeAgo from 'javascript-time-ago'
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en'

// Add locale-specific relative date/time formatting rules.
TimeAgo.addLocale(en)

// Adapted from Andrey Gordeev's answer at: 
// https://***.com/questions/56245156/timestamp-from-firestore-gets-converted-to-a-map-when-using-cloud-function

class MyClass 
    timeAgo: TimeAgo

    constructor() 
        this.timeAgo = new TimeAgo('en-US')
    

    getTimeText = (timeObject: any) => 
        // Convert to time text once it's of type firestore.Timestamp
        const getTextFromTimestamp = (timestamp: app.firestore.Timestamp) => 
            return this.timeAgo.format(timestamp.toDate())

        
        if (timeObject instanceof app.firestore.Timestamp) 
            // Check if Timestamp (accessed from client SDK)
            return getTextFromTimestamp(timeObject)
         else if (Object.prototype.toString.call(timeObject) === '[object Object]') 
            // Check if it's a Map
            const seconds = timeObject['_seconds']
            const nanoseconds = timeObject['_nanoseconds']
            if (seconds && nanoseconds) 
                const timestamp = new app.firestore.Timestamp(seconds, nanoseconds)
                return getTextFromTimestamp(timestamp)
            
        
        console.log('Couldn\'t parse time', timeObject)
        // Fallback
        return 'some time ago'
    

【讨论】:

【参考方案5】:

我通过将时间戳作为字符串发送解决了我的问题。

"timestamp": DateTime.now().toString()

因为现在我的时间戳现在是字符串,所以我从 JSON 中获取确切的时间戳作为字符串。 现在我所做的是使用名为timeago 的颤振插件将其转换为时间前格式,例如:“10 分钟前”

Text(timeago.format(DateTime.parse(timestamp)).toString);

【讨论】:

我不建议将日期作为字符串存储在 Firestore(或任何其他数据库中,就此而言。您应该坚持使用时间戳,或者如果这不起作用,请使用以unix 纪元时间。【参考方案6】:

我偶然发现了这个问题,因为我试图弄清楚为什么我的云函数没有正确解析时间戳(并将其返回给调用者)。事实上,在登录时,我注意到我的日期字段显示为 Timestamp _seconds: N, _nanoseconds: NN

解决方案是在使用 Timestamp 类之前简单地将必要的字段转换为它。否则会返回地图:

const date = <field> as admin.firestore.Timestamp

【讨论】:

【参考方案7】:
print(expiryObj); //_nanoseconds: 748000000, _seconds: 1612641862

Timestamp tempstamp = Timestamp(expiryObj['_seconds'], expiryObj['_nanoseconds']);
DateTime expiryDateTime = tempstamp.toDate();

print(expiryDateTime); //2021-02-06 15:04:22.748

(import import 'package:cloud_firestore/cloud_firestore.dart';)

【讨论】:

以上是关于使用云功能时,来自 Firestore 的时间戳会转换为地图的主要内容,如果未能解决你的问题,请参考以下文章

Firebase云功能Firestore触发onWrite在本地测试时未按预期运行

通过云功能运行时在 Firestore 中出现问题

Firestore - 云功能 - 获取 uid

在云功能上使用 Firestore SDK 时出现错误:无法编码类型

使用云功能更新 Firestore 文档

Firebase 云功能 / Firestore