1. 程式人生 > >MongoDB C#:如何將包含DateTime的JSON反序列化為正確的BsonDocument DateTime值

MongoDB C#:如何將包含DateTime的JSON反序列化為正確的BsonDocument DateTime值

目錄

介紹

背景

使用程式碼

興趣點


如果您的Json包含日期時間值如“2018-11-23T205605.3117673Z”並且您需要將其作為正確的BsonDateTime值放在BsonDocument中,該怎麼辦?

介紹

這是我的第一篇文章,最後我發現了一個問題,我認為在這個領域(JSONDateTime和使用BsonDocumentMongoDb)中有很多人都有值得發表的問題!我很高興與您分享解決方案!

大多數時候,人們使用Json作為序列化格式在應用程式之間傳輸資料。MongoDB有內建的序列化器,可以從Json轉換為Bson。問題是生成的Bson

不會將預期的DateTime 字串處理為BsonType.DateTime ,而是將其處理為BsonType.String。我想給你一個關於如何構建東西的選項,將Json字串值(具有給定DateTime格式)正確地反序列化為BsonDateTime值。

背景

MongoDb中內建的序列化程式可以在滿足給定格式時處理Json中的datetime字串表示,即:

{
  "myDate": ISODate("2018-11-23T20:56:05.311Z")
}

但這不是一個有效的Json格式,並且沒有Json序列化程式(如Json.NET)能夠正確地序列化或反序列化(無需任何修改)。大多數人都希望擁有以下

Json格式:

{
  "myDate": "2018-11-23T20:56:05.311Z"
}

通常,您可以使用以下程式碼將Json反序列化為Bson

var bson = BsonSerializer.Deserialize<BsonDocument>(json);

你會看到結果是BsonType.String

當您檢查BsonSerializer原始碼時,您將找到以下程式碼:

public static TNominalType Deserialize<TNominalType>
    (string json, Action<BsonDeserializationContext.Builder> configurator = null)
{
   using (var bsonReader = new JsonReader(json))
   {
      return Deserialize<TNominalType>(bsonReader, configurator);
   }
}

所以在幕後,MongoDb使用某種JsonReader,只是解析任何string值為BsonType.String

這是我們將擴充套件JsonReader的地方,以便能夠正確地解析上面提到的datetime模式,從而產生一個BsonType.DateTime

使用程式碼

讓我們建立自己的DateTimeAwareJsonReader,只是擴充套件現有的JsonReader並嘗試確定給定string值是否可以是有效的日期時間。但我們要記住,我們希望它儘可能高效。我們知道我們必須調查Json值的每個string表示,無論它是否是datetime。讓我們開始吧:

public class DateTimeAwareJsonReader : JsonReader
{
    public DateTimeAwareJsonReader(string json) : base(json)
    {
    }

    public DateTimeAwareJsonReader(TextReader textReader) : base(textReader)
    {
    }

    public DateTimeAwareJsonReader(string json, JsonReaderSettings settings) : base(json, settings)
    {
    }

    public DateTimeAwareJsonReader(TextReader textReader, JsonReaderSettings settings) : 
                                   base(textReader, settings)
    {
    }
}

序列化引擎使用IBsonReader.ReadBsonType()以確定當前正在解析哪種型別,因此我們必須掛鉤並在此處進行調查,無論我們是否想告訴引擎給定的值是 stringdatetime所以讓我們新增一個重寫方法:

private string _currentStringValue;
private BsonDateTime _currentDateTime;

public override BsonType ReadBsonType()
{
    _currentDateTime = null;
    var currentBsonType = base.ReadBsonType();
    if (currentBsonType == BsonType.String)
    {
        var previousState = State;
        _currentStringValue = ReadString();
        State = previousState;
        // date pattern is YYYY-MM-dd, if this pattern is met, 
        // we assume it is a DateTime and try to parse.
        // kind of like duck typing..if it walks like a duck and talks like a duck, 
        // it must be a duck.. :D
        if (_currentStringValue.Length > 9)
        {
            if (char.IsDigit(_currentStringValue[0]) &&
                char.IsDigit(_currentStringValue[1]) &&
                char.IsDigit(_currentStringValue[2]) &&
                char.IsDigit(_currentStringValue[3]) &&
                _currentStringValue[4].Equals('-') &&
                char.IsDigit(_currentStringValue[5]) &&
                char.IsDigit(_currentStringValue[6]) &&
                _currentStringValue[7].Equals('-') &&
                char.IsDigit(_currentStringValue[8]) &&
                char.IsDigit(_currentStringValue[9]))
            {
                // looks like a date string
                if (DateTime.TryParse(_currentStringValue, out var parsedDateTime))
                {
                    _currentDateTime = new BsonDateTime(parsedDateTime);
                    CurrentBsonType = BsonType.DateTime; // we have to set also the 
                                                         // current bson type and return it.
                    return BsonType.DateTime;
                }
            }
        }
    }
    return currentBsonType;
}

基礎的JsonReader將告訴我們,我們的“ myDate屬性是BsonType.String。在這種情況下,我們想要攔截並進一步的調查。為了提高效率,我們只檢查至少給定大小的值。我希望序列化datetime的格式至少為“ YYYY-MM-dd(例如“ 2018-05-02)。如果您有其他要求,可以根據需要在此處調整邏輯。所以一旦我們在這裡涉及到數字,並且所有內容看起來都像是string表示的datetime,我們將告訴序列化器這個值是BsonType.DateTime,否則我們想要回退到序列化器必須決定的情況。

一旦我們認為給定的值是DateTime,我們必須重寫另一個方法:

public override long ReadDateTime()
{
    if (_currentDateTime == null)
    {
        return base.ReadDateTime();
    }
            
    if (Disposed) { ThrowObjectDisposedException(); }
    VerifyBsonType("ReadDateTime", BsonType.DateTime);
    State = BsonReaderState.Type;
    return _currentDateTime.AsBsonDateTime.MillisecondsSinceEpoch;
}

如果我們的邏輯開始,該欄位_currentDateTime將具有解析的值,我們將返回它。

這就是它!您現在有一個可以正確計算datetime 字串表示的工作解決方案,並告訴MongoDb它將其作為一個DateTime處理。

要測試它,你可以做一個簡單的控制檯應用程式:

class Program
{
    public class SomeModel
    {
        [JsonProperty("id")]
        public int Id { get; set; }
        [JsonProperty("test")]
        public string Test { get; set; }
        [JsonProperty("today")]
        public DateTime Today { get; set; }
        [JsonProperty("inner")]
        public SomeModel Inner { get; set; }
    }

    static void Main(string[] args)
    {
        var model = new SomeModel
        {
            Id = 1,
            Test = "Model",
            Today = DateTime.UtcNow.Date,
            Inner = new SomeModel
            {
                Id = 2,
                Test = "Inner",
                Today = DateTime.UtcNow
            }
        };

        var json = JsonConvert.SerializeObject(model);
        using (var reader = new DateTimeAwareJsonReader(json))
        {
            var bson = BsonSerializer.Deserialize<BsonDocument>(reader);
            Console.WriteLine("Models today property is: {0}", bson["today"].BsonType);
        }
        Console.ReadLine();
    }
}

興趣點

一旦我弄清楚MongoDb序列化是如何工作的,就很容易構建一個攔截機制來告訴底層的序列化框架datetime應該是什麼。我還沒有在高效的環境中測試它,但我很快就會做到。更有趣的部分是以一種精益的方式來猜測一個string值是否可以是datetime最有效的方式,而不會過多地干擾MongoDb客戶端庫。我認為這個解決方案適用於大多數用例。期待您的意見!;-)

 

原文地址:https://www.codeproject.com/Tips/1268019/MongoDB-Csharp-How-to-Deserialize-a-JSON-Containin