SmartCodable - Swift數(shù)據(jù)解析的智能解決方案

???github SmartCodable 看起來還不錯申屹?給個star?吧焙贷,急需支持???

SmartCodable - Swift數(shù)據(jù)解析的智能解決方案

SmartCodable 是一個基于Swift的Codable協(xié)議的數(shù)據(jù)解析庫拍皮,旨在提供更為強大和靈活的解析能力歹叮。通過優(yōu)化和擴展Codable的標準功能跑杭,SmartCodable 有效地解決了傳統(tǒng)解析過程中的常見問題,并提高了解析的容錯性和靈活性咆耿。

為何選擇SmartCodable?

在使用標準的Codable進行數(shù)據(jù)解析時德谅,開發(fā)者常常會遇到諸如鍵不存在、類型不匹配或值為null等問題萨螺,這些問題往往會導致整個解析過程的失敗窄做,并拋出異常。SmartCodable 針對這些挑戰(zhàn)提供了智能化的解決方案慰技,確保解析過程的健壯性和流暢性椭盏。

主要特性

  • 增強的錯誤處理:當遇到鍵不存在、類型不匹配或值為null等問題時吻商,不會立即中斷解析掏颊,而是提供了更為靈活的處理選項。
  • 值類型轉(zhuǎn)換:如果目標類型與實際類型不符但可以進行有意義的轉(zhuǎn)換艾帐,SmartCodable 會自動轉(zhuǎn)換值類型乌叶,確保數(shù)據(jù)的正確解析。
  • 默認值填充:當某個屬性無法解析時柒爸,SmartCodable 允許自動填充該屬性類型的默認值枉昏,例如將Bool類型的字段默認設為false,從而避免了整個解析過程的失敗揍鸟。
  • 兼容性和靈活性SmartCodable 完全兼容標準的Codable協(xié)議兄裂,并在此基礎(chǔ)上提供更多的定制化選項,適應更復雜和多變的數(shù)據(jù)解析需求阳藻。

解析效率

image.png

使用這樣的數(shù)組晰奖,數(shù)組的元素項分別設置為: 100個,1000個腥泥,10000個匾南。分別對這五種解析方案進行解析耗時的統(tǒng)計。

[
    {
        "name": "Anaa Airport",
        "iata": "AAA",
        "icao": "NTGA",
        "coordinates": [-145.51222222222222, -17.348888888888887],
        "runways": [
            {
                "direction": "14L/32R",
                "distance": 1502,
                "surface": "flexible"
            }
        ]
    }
]

理論上SmartCodable的解析效率是低于Codable的蛔外。如果不解析 runways 蛆楞,就是如此。
SmartCodable對于枚舉項的解析更加高效夹厌。所以在本次數(shù)據(jù)對比中豹爹,解析效率最高,甚至高于Codable矛纹。

作者使用的是單元測試中的 measure 函數(shù)進行性能測算臂聋。

struct Smart: SmartCodable {
    
    var name: String?
    var iata: String?
    var icao: String?
    var coordinates: [Double]?
    var runways: [Runway]?
    
    struct Runway: SmartCodable {
        var direction: String?
        var distance: Int?
        var surface: Surface?
    }
}

func testSmart() {
    measure {
        guard let objects = [Smart].deserialize(data: data)  else {
            return
        }
        XCTAssertEqual(objects.count, count)
    }
}

Demo工程中提供了測試用例,請自行下載工程代碼,訪問 Tests.swift 文件孩等。

HandyJSON vs Codable

CodableHandyJSON是兩種常用的方法艾君。

  • HandyJSON 使用Swift的反射特性來實現(xiàn)數(shù)據(jù)的序列化和反序列化。該機制是非法的肄方,不安全的冰垄, 更多的細節(jié),可以訪問 HandyJSON 的466號issue.

  • Codable 是Swift標準庫的一部分权她,提供了一種聲明式的方式來進行序列化和反序列化播演,它更為通用。

比較這兩者在性能方面的差異需要考慮不同的數(shù)據(jù)類型和場景伴奥。一般而言写烤,Codable在以下情況下可能具有比HandyJSON更低的解析耗時:

  1. 標準的JSON結(jié)構(gòu): 當解析標準且格式良好的JSON數(shù)據(jù)時,Codable通常表現(xiàn)出較好的性能拾徙。這是因為Codable是Swift標準庫的一部分洲炊,得到了編譯器的優(yōu)化。
  2. 復雜數(shù)據(jù)模型: 對于包含多層嵌套和復雜數(shù)據(jù)結(jié)構(gòu)的JSON尼啡,Codable可能比HandyJSON更有效暂衡,特別是在類型安全和編譯時檢查方面。
  3. 類型安全性高的場景: Codable提供了更強的類型安全性崖瞭,這有助于在編譯時捕捉錯誤狂巢。在處理嚴格遵循特定模型的數(shù)據(jù)時,這種類型檢查可能帶來性能優(yōu)勢书聚。
  4. 與Swift特性集成: Codable與Swift的其他特性(如類型推斷唧领、泛型等)集成得更緊密,這可能在某些情況下提高解析效率雌续。

然而斩个,這些差異并不是絕對的。HandyJSON在某些情況下(如處理動態(tài)或非結(jié)構(gòu)化的JSON數(shù)據(jù))可能表現(xiàn)得更好驯杜。性能也會受到JSON數(shù)據(jù)的大小和復雜性受啥、應用的具體實現(xiàn)方式以及運行時環(huán)境等因素的影響。實際應用中鸽心,選擇CodableHandyJSON應基于具體的項目需求和上下文滚局。

如何使用SmartCodable?

使用SmartCodable與使用標準的Codable類似,但你會獲得額外的錯誤處理能力和更加靈活的解析選項顽频。只需將你的數(shù)據(jù)模型遵循SmartCodable協(xié)議藤肢,即可開始享受更加智能的數(shù)據(jù)解析體驗。

cocopods集成

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '11.0'
use_frameworks!

target 'MyApp' do
  pod 'SmartCodable'
end

字典類型的解碼

import SmartCodable

struct Model: SmartCodable {
    var name: String = ""
}

let dict: [String: String] = ["name": "xiaoming"]
guard let model = Model.deserialize(dict: dict) else { return }

數(shù)組類型解碼

import SmartCodable

struct Model: SmartCodable {
    var name: String = ""
}

let dict: [String: String] = ["name": "xiaoming"]
let arr = [dict, dict]
guard let models = [Model].deserialize(array: arr) else { return }

序列化與反序列化

// 字典轉(zhuǎn)模型
guard let xiaoMing = JsonToModel.deserialize(dict: dict) else { return }

// 模型轉(zhuǎn)字典
let studentDict = xiaoMing.toDictionary() ?? [:]

// 模型轉(zhuǎn)json字符串
let json1 = xiaoMing.toJSONString(prettyPrint: true) ?? ""

// json字符串轉(zhuǎn)模型
guard let xiaoMing2 = JsonToModel.deserialize(json: json1) else { return }

解析完成的回調(diào)

class Model: SmartDecodable {

    var name: String = ""
    var age: Int = 0
    var desc: String = ""
    required init() { }
    
    // 解析完成的回調(diào)
    func didFinishMapping() {    
        if name.isEmpty {
            desc = "\(age)歲的" + "人"
        } else {
            desc = "\(age)歲的" + name
        }
    }
}

枚舉的解碼

struct CompatibleEnum: SmartCodable {

    init() { }
    var enumTest: TestEnum = .a

    enum TestEnum: String, SmartCaseDefaultable {
        static var defaultCase: TestEnum = .a

        case a
        case b
        case hello = "c"
    }
}

讓你的枚舉遵守 SmartCaseDefaultable 協(xié)議冲九,如果枚舉解析失敗谤草,將使用defaultCase作為默認值。

解碼Any

Codable是無法解碼Any類型的莺奸,這樣就意味著模型的屬性類型可以為 Any丑孩,[Any][String: Any]灭贷。 這對解碼的便利性造成了一定的困擾温学。

官方的解決方案

對非原生類型字段,給它再生成一個struct甚疟,用原生類型來表述屬性就行仗岖。

struct Block: Codable {
    let message: String
    let index: Int
    let transactions: [[String: Any]]
    let proof: String
    let previous_hash: String
}

改為:

struct Transaction: Codable {
    let amount: Int
    let recipient: String
    let sender: String
}
 
struct Block: Codable {
    let message: String
    let index: Int
    let transactions: [Transaction]
    let proof: String
    let previous_hash: String
}

使用泛型

如果情況允許,可以使用泛型來代替览妖。

struct AboutAny<T: Codable>: SmartCodable {
    init() { }

    var dict1: [String: T] = [:]
    var dict2: [String: T] = [:]
}
guard let one = AboutAny<String>.deserialize(dict: dict) else { return }

使用 SmartAny

SmartAnySmartCodable 提供了一種方案轧拄。SmartAny是枚舉類型。

public enum SmartAny {
    case bool(Bool)
    case string(String)
    case double(Double)
    case cgFloat(CGFloat)
    case float(Float)
    case int(Int)
    case int8(Int8)
    case int16(Int16)
    case int32(Int32)
    case int64(Int64)
    case uInt(Int)
    case uInt8(UInt8)
    case uInt16(UInt16)
    case uInt32(UInt32)
    case uInt64(UInt64)    
    case dict([String: SmartAny])
    case array([SmartAny])
}

重寫了public init(from decoder: Decoder) throws方法讽膏,將解析完成的數(shù)據(jù)包裹進SmartAny內(nèi)檩电。

extension SmartAny: Codable {
    // 實現(xiàn) Codable
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        
        
        if let value = try? container.decode(Bool.self) {
            self = .bool(value)
        } else if let value = try? container.decode(String.self) {
            self = .string(value)
        }
        其他代碼
        ......
    }
}

如果你要獲取原始的數(shù)據(jù),可以使用 peel 方法去殼府树。

extension Dictionary where Key == String, Value == SmartAny {
    /// 解析完成會被SmartAny包裹俐末,使用該屬性去殼。
    public var peel: [String: Any] {
        var temp: [String: Any] = [:]
        for (key, value) in self {
            temp.updateValue(value.peel, forKey: key)
        }
        return temp
    }
}

extension Array where Element == SmartAny {
    /// 解析完成會被SmartAny包裹奄侠,使用該屬性去殼卓箫。
    public var peel: [Any] {
        var temp: [Any] = []
        
        temp = self.map({
            $0.peel
        })
        return temp
    }
}

extension SmartAny {
    /// 獲取原本的值
    public var peel: Any {
        switch self {
        case .bool(let v):
            return v
            
        case .string(let v):
            return v
        
        // 其他代碼
        ......
        }
    }
}

更多實現(xiàn)細節(jié)可以訪問SmartAny.swift 文件。

解碼策略 - SmartDecodingOption

public static func deserialize(json: String?, options: [SmartDecodingOption]? = nil) -> Self? 

options提供了四種解碼選項垄潮,分別為:

    /// 用于在解碼之前自動更改密鑰值的策略
    case keyStrategy(KeyDecodingStrategy)
    
    /// 用于解碼 “Date” 值的策略
    case dateStrategy(JSONDecoder.DateDecodingStrategy)
    
    /// 用于解碼 “Data” 值的策略
    case dataStrategy(JSONDecoder.DataDecodingStrategy)
    
    /// 用于不符合json的浮點值(IEEE 754無窮大和NaN)的策略
    case floatStrategy(JSONDecoder.NonConformingFloatDecodingStrategy)

SmartKeyDecodingStrategy

/// key解碼策略
public enum SmartKeyDecodingStrategy {
    case useDefaultKeys
    case convertFromSnakeCase
    case custom([String: String])
}
  • useDefaultKeys: 使用默認的解析映射方式烹卒。

  • convertFromSnakeCase: 轉(zhuǎn)駝峰的命名方式。會將本次解析的字段弯洗,全部轉(zhuǎn)成駝峰命名甫题。

  • custom: 自定義的方式。key是數(shù)據(jù)中的字段名涂召,value是模型中的屬性名坠非。

// 1. CodingKeys 映射
guard let feedOne = FeedOne.deserialize(json: json) else { return }

// 2.  使用keyDecodingStrategy的駝峰命名
guard let feedTwo = FeedTwo.deserialize(json: json, options: [.keyStrategy(.convertFromSnakeCase)]) else { return }



// 3. 使用keyDecodingStrategy的自定義策略
let option: SmartDecodingOption = .keyStrategy(.custom(["nick_name": "name"]))
guard let feedThree = FeedThree.deserialize(json: json, options: [option]) else { return }

Date格式的解碼

let json = """
{
   "birth": "2034-12-01 18:00:00"
}
"""
let dateFormatter = DateFormatter()
 dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let option: SmartDecodingOption = .dateStrategy(.formatted(dateFormatter))

guard let model = FeedOne.deserialize(json: json, options: [option]) else { return }

Data類型的解碼

let json = """
{
   "address": "aHR0cHM6Ly93d3cucWl4aW4uY29t"
}
"""

let option: SmartDecodingOption = .dataStrategy(.base64)
guard let model = FeedOne.deserialize(json: json, options: [option]) else { return }

if let data = model.address, let url = String(data: data, encoding: .utf8) {
    print(url)
    // https://www.qixin.com
}

SmartCodable的兼容性

在使用系統(tǒng)的 Codable 解碼的時候,遇到 無鍵果正,值為null炎码, 值類型錯誤 拋出異常導致解析失敗。SmartCodable 底層默認對這三種解析錯誤進行了兼容秋泳。

具體的實現(xiàn)邏輯可以查看 Patcher.swift 文件潦闲。

無鍵 & 值為null

這兩種情況的數(shù)據(jù),我稱之為擺爛數(shù)據(jù)迫皱,這種數(shù)據(jù)無法拯救歉闰。

解析到 無鍵 & 值為null 的時候辖众,SmartCodable會提供該字段類型的默認值進行解析填充(如果是可選類型,提供nil)凹炸。使解析順利進行。

對這兩份數(shù)據(jù)舱痘,解析到 CompatibleTypes 模型中

var json: String {
   """
   {
   }
   """
}
var json: String {
   """
   {
     "a": null,
     "b": null,
     "c": null,
     "d": null,
     "e": null,
     "f": null,
     "g": null,
     "h": null,
     "i": null,
     "j": null,
     "k": null,
     "l": null,

     "v": null,
     "w": null,
     "x": null,
     "y": null,
     "z": null
   }
   """
}
struct CompatibleTypes: SmartDecodable {

    var a: String = ""
    var b: Bool = false
    var c: Date = Date()
    var d: Data = Data()

    var e: Double = 0.0
    var f: Float = 0.0
    var g: CGFloat = 0.0

    var h: Int = 0
    var i: Int8 = 0
    var j: Int16 = 0
    var k: Int32 = 0
    var l: Int64 = 0

    var m: UInt = 0
    var n: UInt8 = 0
    var o: UInt16 = 0
    var p: UInt32 = 0
    var q: UInt64 = 0

    var v: [String] = []
    var w: [String: [String: Int]] = [:]
    var x: [String: String] = [:]
    var y: [String: Int] = [:]
    var z: CompatibleItem = CompatibleItem()
}

class CompatibleItem: SmartDecodable {
    var name: String = ""
    var age: Int = 0   
    required init() { }
}

解析完成后塌碌,將使用該屬性對應的數(shù)據(jù)類型的默認值進行填充。

guard let person = CompatibleTypes.deserialize(json: json) else { return }
/**
 "屬性:a 的類型是 String, 其值為 "
 "屬性:b 的類型是 Bool搂漠, 其值為 false"
 "屬性:c 的類型是 Date, 其值為 2001-01-01 00:00:00 +0000"
 "屬性:d 的類型是 Data, 其值為 0 bytes"
 "屬性:e 的類型是 Double拣度, 其值為 0.0"
 "屬性:f 的類型是 Float奸晴, 其值為 0.0"
 "屬性:g 的類型是 CGFloat逮光, 其值為 0.0"
 "屬性:h 的類型是 Int嗡综, 其值為 0"
 "屬性:i 的類型是 Int8碑幅, 其值為 0"
 "屬性:j 的類型是 Int16, 其值為 0"
 "屬性:k 的類型是 Int32裹赴, 其值為 0"
 "屬性:l 的類型是 Int64雷猪, 其值為 0"
 "屬性:m 的類型是 UInt, 其值為 0"
 "屬性:n 的類型是 UInt8验夯, 其值為 0"
 "屬性:o 的類型是 UInt16共屈, 其值為 0"
 "屬性:p 的類型是 UInt32, 其值為 0"
 "屬性:q 的類型是 UInt64泼掠, 其值為 0"
 "屬性:v 的類型是 Array<String>括改, 其值為 []"
 "屬性:w 的類型是 Dictionary<String, Dictionary<String, Int>>, 其值為 [:]"
 "屬性:x 的類型是 Dictionary<String, String>, 其值為 [:]"
 "屬性:y 的類型是 Dictionary<String, Int>对粪, 其值為 [:]"
 "屬性:z 的類型是 CompatibleItem右冻, 其值為 CompatibleItem(name: \"\", age: 0)"
 */

值類型錯誤

這種情況的數(shù)據(jù)著拭,我稱之為可拯救的數(shù)據(jù)。例如: Model中定義的Bool類型,數(shù)據(jù)中返回的是Int類型的0或1爱榔,String類型的True/true/Yes/No等版姑。

解析到 值類型錯誤 的時候,SmartCodable會嘗試對數(shù)據(jù)值進行類型轉(zhuǎn)換宪肖,如果轉(zhuǎn)換成功健爬,將使用該值娜遵。如果轉(zhuǎn)換失敗,將使用該屬性對應的數(shù)據(jù)類型的默認值進行填充壤短。

Bool類型的轉(zhuǎn)換

/// 兼容Bool類型的值设拟,Model中定義為Bool類型,但是數(shù)據(jù)中是String久脯,Int的情況纳胧。
static func compatibleBoolType(value: Any) -> Bool? {
    switch value {
    case let intValue as Int:
        if intValue == 1 {
            return true
        } else if intValue == 0 {
            return false
        } else {
             return nil
        }
    case let stringValue as String:
        switch stringValue {
        case "1", "YES", "Yes", "yes", "TRUE", "True", "true":
            return true
        case "0",  "NO", "No", "no", "FALSE", "False", "false":
            return false
        default:
            return nil
        }
    default:
        return nil
    }
}

String類型的轉(zhuǎn)換

/// 兼容String類型的值
static func compatibleStringType(value: Any) -> String? {
    
    switch value {
    case let intValue as Int:
        let string = String(intValue)
        return string
    case let floatValue as Float:
        let string = String(floatValue)
        return string
    case let doubleValue as Double:
        let string = String(doubleValue)
        return string
    default:
        return nil
    }
}

其他更多類型

請查看 TypePatcher.swift 了解更多。

調(diào)試日志

SmartCodable鼓勵從根本上解決解析中的問題桶现,即:不需要用到SmartCodable的兼容邏輯躲雅。 如果出現(xiàn)解析兼容的情況鼎姊,修改Model中屬性的定義骡和,或要求數(shù)據(jù)方進行修正。為了更方便的定位問題相寇,SmartCodable提供了便捷的解析錯誤日志慰于。

調(diào)試日志,將提供輔助信息唤衫,幫助定位問題:

  • 錯誤類型: 錯誤的類型信息
  • 模型名稱:發(fā)生錯誤的模型名出
  • 數(shù)據(jù)節(jié)點:發(fā)生錯誤時婆赠,數(shù)據(jù)的解碼路徑。
  • 屬性信息:發(fā)生錯誤的字段名佳励。
  • 錯誤原因: 錯誤的具體原因休里。
================ [SmartLog Error] ================
錯誤類型: '找不到鍵的錯誤' 
模型名稱:Array<Class> 
數(shù)據(jù)節(jié)點:Index 0 → students → Index 0
屬性信息:(名稱)more
錯誤原因: No value associated with key CodingKeys(stringValue: "more", intValue: nil) ("more").
==================================================

================ [SmartLog Error] ================
錯誤類型: '值類型不匹配的錯誤' 
模型名稱:DecodeErrorPrint 
數(shù)據(jù)節(jié)點:a
屬性信息:(類型)Bool (名稱)a
錯誤原因: Expected to decode Bool but found a string/data instead.
==================================================


================ [SmartLog Error] ================
錯誤類型: '找不到值的錯誤' 
模型名稱:DecodeErrorPrint 
數(shù)據(jù)節(jié)點:c
屬性信息:(類型)Bool (名稱)c
錯誤原因:  c 在json中對應的值是null
==================================================

你可以通過SmartConfig 調(diào)整日志的相關(guān)設置。

如何理解數(shù)據(jù)節(jié)點赃承?
image.png

右側(cè)的數(shù)據(jù)是數(shù)組類型妙黍。注意標紅的內(nèi)容,由外到里對照查看瞧剖。

  • Index 0: 數(shù)組的下標為0的元素拭嫁。

  • sampleFive: 下標為0的元素對應的是字典,即字典key為sampleFive對應的值(是一個數(shù)組)抓于。

  • Index 1:數(shù)組的下標為1的元素.

  • sampleOne:字典中key為sampleOne對應的值做粤。

  • string:字典中key為sring對應的值。

四. SamrtCodable的缺點

1. 可選模型屬性

struct Feed: SmartCodable {
    var one: FeedOne?
}
struct FeedOne: SmartCodable {
    var name: String = ""
}

如有模型中的屬性是嵌套的模型屬性捉撮,遇到類型不匹配的情況怕品,Codable無法解析拋出異常,這種情況的異常巾遭,SmartCodale將無法兼容肉康。

此時您有兩種選擇:

  • 將Feed中one這個屬性設置為非可選的修己。 SmartCodable 將正常工作。
  • 將該屬性使用 @SmartOptional 屬性包裝器修飾迎罗。
struct Feed: SmartCodable {
    @SmartOptional var one: FeedOne?
}

class FeedOne: SmartCodable {
    var name: String = ""
    required init() { }
}

這是一個不得已的實現(xiàn)方案:

為了做解碼失敗的兼容睬愤,我們重寫了 KeyedEncodingContainerdecodedecodeIfPresent 方法。

需要注意的是:decodeIfPresent底層的實現(xiàn)仍是使用的 decode纹安。

// 系統(tǒng)Codable源碼實現(xiàn)
public extension KeyedDecodingContainerProtocol {
    @_inlineable // FIXME(sil-serialize-all)
    public func decodeIfPresent(_ type: Bool.Type, forKey key: Key) throws -> Bool? {
        guard try self.contains(key) && !self.decodeNil(forKey: key) else { return nil }

        return try self.decode(Bool.self, forKey: key)
    }
}

KeyedEncodingContainer容器是用結(jié)構(gòu)體實現(xiàn)的尤辱。 重寫了結(jié)構(gòu)體的方法之后,沒辦法再調(diào)用父方法厢岂。

  1. 這種情況下光督,如果再重寫 public func decodeIfPresent<T>(_ type: T.Type, forKey key: K) throws -> T? 方法,就會導致方法的循環(huán)調(diào)用塔粒。
  2. 使用SmartOptional屬性包裝器修飾可選屬性结借,被修飾后會產(chǎn)生一個新的類型,對此類型解碼就不會走decodeIfPresent卒茬,而是會走decode方法船老。

使用SmartOptional的三個限制條件

  • 必須遵循SmartDecodable協(xié)議

  • 必須是可選屬性

    如果不是可選屬性,就沒必要使用SmartOptional圃酵。

  • 必須是class類型

    如果模型是Struct柳畔,是值類型。在執(zhí)行 didFinishMapping 的時候郭赐,無法初始化被屬性包裝器修飾的屬性薪韩,進而無法有效的執(zhí)行解碼完成之后的值修改。

如果你有更好的方案捌锭,可以提issue俘陷。

2. 模型中設置的默認值無效

Codable在進行解碼的時候,是無法知道這個屬性的观谦。所以在decode的時候拉盾,如果解析失敗,使用默認值進行填充時坎匿,拿不到這個默認值盾剩。再處理解碼兼容時,只能自己生成一個對應類型的默認值填充替蔬。

如果你有更好的方案告私,可以提issue。

進一步了解

我們提供了詳細的示例工程承桥,可以下載工程代碼查看驻粟。

image.png

加入我們

SmartCodable 是一個開源項目,我們歡迎所有對提高數(shù)據(jù)解析性能和健壯性感興趣的開發(fā)者加入。無論是使用反饋蜀撑、功能建議還是代碼貢獻挤巡,你的參與都將極大地推動 SmartCodable 項目的發(fā)展。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末酷麦,一起剝皮案震驚了整個濱河市矿卑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沃饶,老刑警劉巖母廷,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異糊肤,居然都是意外死亡琴昆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門馆揉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來业舍,“玉大人,你說我怎么就攤上這事升酣∠夏海” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵拗踢,是天一觀的道長脚牍。 經(jīng)常有香客問我向臀,道長巢墅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任券膀,我火速辦了婚禮君纫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芹彬。我一直安慰自己蓄髓,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布舒帮。 她就那樣靜靜地躺著会喝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玩郊。 梳的紋絲不亂的頭發(fā)上肢执,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音译红,去河邊找鬼预茄。 笑死,一個胖子當著我的面吹牛侦厚,可吹牛的內(nèi)容都是我干的耻陕。 我是一名探鬼主播拙徽,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诗宣!你這毒婦竟也來了膘怕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤召庞,失蹤者是張志新(化名)和其女友劉穎淳蔼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裁眯,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡鹉梨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了穿稳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片存皂。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逢艘,靈堂內(nèi)的尸體忽然破棺而出旦袋,到底是詐尸還是另有隱情,我是刑警寧澤它改,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布呼股,位于F島的核電站坡垫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜读处,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一谭贪、第九天 我趴在偏房一處隱蔽的房頂上張望咙冗。 院中可真熱鬧粟按,春花似錦、人聲如沸遏餐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽失都。三九已至柏蘑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粹庞,已是汗流浹背咳焚。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留信粮,地道東北人黔攒。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親督惰。 傳聞我的和親對象是個殘疾皇子不傅,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容