JSON for Modern C++


转到github

nlohmann::json在项目中的实际使用详见C++静态代码检测工具:cppdetector

设计目标

已经有无数的JSON库,每个库都有其存在的理由。我们有这些设计目标:

  • 直观的语法。在像Python这样的语言中,JSON感觉就像是一级数据类型。我们使用现代C++的操作魔法来实现类似普通代码般的感觉。看看下面的例子,你就会明白我的意思。

  • 整合。我们的整个代码只有一个头文件json.hpp。没有库,没有子项目,没有依赖项,没有复杂的构建系统,仅仅使用标准C ++ 11编写。总而言之,不需要调整任何编译器标志或项目设置。

  • 压力测试。我们的类经过严格的单元测试,涵盖了100%的代码,包括所有特殊行为。此外,我们使用ValgrindClang Sanitizers做了检测,没有内存泄漏。还使用谷歌OSS-Fuzz对所有解析器进行24/7模糊测试,到目前为止有效地执行了数十亿次测试。为了保持高质量,该项目遵循核心基础设施倡议(CII)的最佳实践

对我们来说并不那么重要的其他方面:

  • 内存效率。每个JSON对象都有一个指针(联合的最大大小)和一个枚举元素(1个字节)的开销。默认泛化使用以下C ++数据类型:std::string用于字符串,int64_tuint64_tdouble用于数字,std::map用于对象,std::vector用于数组和bool用于布尔值。但是,您可以根据需要特化basic_json通用类。

  • 速度。肯定有更快的JSON库。但是,如果您的目标是通过添加单个头文件添加JSON支持来加速开发,那么这个库就是您的选择。如果您知道如何使用std::vectorstd::map,你就已经准备好了。

有关详细信息,请参阅贡献指南

  • 整合
    json.hpp唯一所需的文件,在目录single_include/nlohmann这里。你需要添加:
#include <nlohmann/json.hpp>

// for convenience
using json = nlohmann::json;

到您要处理JSON的文件时,设置必要的开关以启用C ++ 11(例如,-std=c++11对于GCC和Clang)。

您可以进一步使用文件include/nlohmann/json_fwd.hpp进行前向声明。安装json_fwd.hpp(作为cmake安装步骤的一部分),可以通过设置-DJSON_MultipleHeaders=ON来实现。

包管理器

🍺如果您使用的是OS X和Homebrew,只需键入brew tap nlohmann/jsonbrew install nlohmann_json设置即可。如果您想要最新版本而不是最新版本,请使用brew install nlohmann_json --HEAD

如果您正在使用Meson Build System,那么您可以将此存储库包装为子项目。

如果您使用Conan来管理您的依赖项,只需添加jsonformoderncpp/x.y.z@vthiery/stableconanfile.py的要求,x.y.z您要使用的发行版本在哪里。如果您遇到包装问题,请在此处提出问题。

如果您使用Spack来管理依赖项,则可以使用该nlohmann_json程序包。有关包装的任何问题,请参阅spack项目

如果你在项目中使用猎人来获取外部依赖关系,那么你可以使用nlohmann_json包。有关包装的任何问题,请参阅猎人项目。

如果您使用的是Buckaroo,则可以安装此库的模块buckaroo install nlohmann/json。请在这里提出问题。

如果您在项目中使用vcpkg来获取外部依赖项,那么您可以使用nlohmann-json包。有关包装的任何问题,请参阅vcpkg项目。

如果您使用的是cget,则可以安装最新的开发版本cget install nlohmann/json。可以安装特定版本cget install nlohmann/json@v3.1.0。此外,可以通过添加-DJSON_MultipleHeaders=ON标志(即,cget install nlohmann/json -DJSON_MultipleHeaders=ON)来安装多标头版本。

如果您使用的是CocoaPods,则可以通过将pod添加"nlohmann_json", '~>3.1.2'到podfile 来使用该库(请参阅示例)。请在这里提出问题。

例子

除了下面的示例,您可能需要查看每个函数(包含单独代码示例)的文档(例如,查看emplace())。所有示例文件都可以自己编译和执行(例如,文件emplace.cpp)。

JSON作为一级的数据类型

以下是一些示例,可以让您了解如何使用该类。

假设您要创建如下的JSON对象:

{
  "pi": 3.141,
  "happy": true,
  "name": "Niels",
  "nothing": null,
  "answer": {
    "everything": 42
  },
  "list": [1, 0, 2],
  "object": {
    "currency": "USD",
    "value": 42.99
  }
}

你可以这样写:

// create an empty structure (null)
json j;

// add a number that is stored as double (note the implicit conversion of j to an object)
j["pi"] = 3.141;

// add a Boolean that is stored as bool
j["happy"] = true;

// add a string that is stored as std::string
j["name"] = "Niels";

// add another null object by passing nullptr
j["nothing"] = nullptr;

// add an object inside the object
j["answer"]["everything"] = 42;

// add an array that is stored as std::vector (using an initializer list)
j["list"] = { 1, 0, 2 };

// add another object (using an initializer list of pairs)
j["object"] = { {"currency", "USD"}, {"value", 42.99} };

// instead, you could also write (which looks very similar to the JSON above)
json j2 = {
  {"pi", 3.141},
  {"happy", true},
  {"name", "Niels"},
  {"nothing", nullptr},
  {"answer", {
    {"everything", 42}
  }},
  {"list", {1, 0, 2}},
  {"object", {
    {"currency", "USD"},
    {"value", 42.99}
  }}
};

请注意,在所有这些情况下,您永远不需要“告诉”编译器您要使用哪种JSON值类型。如果你想显性指定类型或表达一些特定的意图,函数:json::arrayjson::object可满足您的需求:

// a way to express the empty array []
json empty_array_explicit = json::array();

// ways to express the empty object {}
json empty_object_implicit = json({});
json empty_object_explicit = json::object();

// a way to express an _array_ of key/value pairs [["currency", "USD"], ["value", 42.99]]
json array_not_object = json::array({ {"currency", "USD"}, {"value", 42.99} });

序列化/反序列化

To/from strings

您可以通过附加_json到字符串来创建JSON值(反序列化):

// create object from string literal
json j = "{ \"happy\": true, \"pi\": 3.141 }"_json;

// or even nicer with a raw string literal
auto j2 = R"(
  {
    "happy": true,
    "pi": 3.141
  }
)"_json;

请注意,如果不附加_json后缀,则不会解析传递的字符串文字,而只是用作JSON字符串值。也就是说,json j = "{ \"happy\": true, \"pi\": 3.141 }"只是存储字符串"{ "happy": true, "pi": 3.141 }"而不是解析实际对象。

以上示例也可以使用json::parse()明确表达:

// parse explicitly
auto j3 = json::parse("{ \"happy\": true, \"pi\": 3.141 }");

您还可以获取JSON值的字符串表示形式(序列化):

// explicit conversion to string
std::string s = j.dump();    // {\"happy\":true,\"pi\":3.141}

// serialization with pretty printing
// pass in the amount of spaces to indent
std::cout << j.dump(4) << std::endl;
// {
//     "happy": true,
//     "pi": 3.141
// }

注意序列化和赋值之间的区别:

// store a string in a JSON value
json j_string = "this is a string";

// retrieve the string value (implicit JSON to std::string conversion)
std::string cpp_string = j_string;
// retrieve the string value (explicit JSON to std::string conversion)
auto cpp_string2 = j_string.get<std::string>();

// retrieve the serialized value (explicit JSON serialization)
std::string serialized_string = j_string.dump();

// output of original string
std::cout << cpp_string << " == " << cpp_string2 << " == " << j_string.get<std::string>() << '\n';
// output of serialized value
std::cout << j_string << " == " << serialized_string << std::endl;

.dump()始终返回序列化值,而.get<std::string>()返回最初存储的字符串值。

请注意,该库仅支持UTF-8。当您在库中存储具有不同编码的字符串时,调用dump()可能会抛出异常。

To/from streams (e.g. files, string streams)

您还可以使用流来序列化和反序列化:

// deserialize from standard input
json j;
std::cin >> j;

// serialize to standard output
std::cout << j;

// the setw manipulator was overloaded to set the indentation for pretty printing
std::cout << std::setw(4) << j << std::endl;

这些运算符适用于std::istreamstd::ostream的任何子类。这是文件的类似示例:

// read a JSON file
std::ifstream i("file.json");
json j;
i >> j;

// write prettified JSON to another file
std::ofstream o("pretty.json");
o << std::setw(4) << j << std::endl;

请注意,为failbit设置异常位不适用此用例。由于使用了noexcept说明符,它将导致程序终止。

从迭代器范围读取

您还可以从迭代器范围解析JSON; 也就是说,其存储内容为连续的字节序列,可从迭代器访问的任何容器,例如std::vector<std::uint8_t>:

std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};
json j = json::parse(v.begin(), v.end());

您也可以去掉范围[begin, end)操作符:

std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};
json j = json::parse(v);

SAX interface(待补充)

STL-like access

我们定义的JSON类的行为与STL容器一样。事实上,它遵循ReversibleContainer规范。

// create an array using push_back
json j;
j.push_back("foo");
j.push_back(1);
j.push_back(true);

// also use emplace_back
j.emplace_back(1.78);

// iterate the array
for (json::iterator it = j.begin(); it != j.end(); ++it) {
  std::cout << *it << '\n';
}

// range-based for
for (auto& element : j) {
  std::cout << element << '\n';
}

// getter/setter
const std::string tmp = j[0];
j[1] = 42;
bool foo = j.at(2);

// comparison
j == "[\"foo\", 1, true]"_json;  // true

// other stuff
j.size();     // 3 entries
j.empty();    // false
j.type();     // json::value_t::array
j.clear();    // the array is empty again

// convenience type checkers
j.is_null();
j.is_boolean();
j.is_number();
j.is_object();
j.is_array();
j.is_string();

// create an object
json o;
o["foo"] = 23;
o["bar"] = false;
o["baz"] = 3.141;

// also use emplace
o.emplace("weather", "sunny");

// special iterator member functions for objects
for (json::iterator it = o.begin(); it != o.end(); ++it) {
  std::cout << it.key() << " : " << it.value() << "\n";
}

// find an entry
if (o.find("foo") != o.end()) {
  // there is an entry with key "foo"
}

// or simpler using count()
int foo_present = o.count("foo"); // 1
int fob_present = o.count("fob"); // 0

// delete an entry
o.erase("foo");

从STL容器转换

任何序列容器(std::array,std::vector,std::deque,std::forward_list,std::list),其值可以被用于构建JSON值(例如,整数,浮点数,布尔值,字符串类型,或者在本节中描述的STL容器)都可被用于创建JSON数组。这同样适用于类似的关联容器(std::set,std::multiset,std::unordered_set,std::unordered_multiset),但是在这些情况下,数组中的元素的顺序取决于元素是如何在各个STL容器排序。

std::vector<int> c_vector {1, 2, 3, 4};
json j_vec(c_vector);
// [1, 2, 3, 4]

std::deque<double> c_deque {1.2, 2.3, 3.4, 5.6};
json j_deque(c_deque);
// [1.2, 2.3, 3.4, 5.6]

std::list<bool> c_list {true, true, false, true};
json j_list(c_list);
// [true, true, false, true]

std::forward_list<int64_t> c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543};
json j_flist(c_flist);
// [12345678909876, 23456789098765, 34567890987654, 45678909876543]

std::array<unsigned long, 4> c_array {{1, 2, 3, 4}};
json j_array(c_array);
// [1, 2, 3, 4]

std::set<std::string> c_set {"one", "two", "three", "four", "one"};
json j_set(c_set); // only one entry for "one" is used
// ["four", "one", "three", "two"]

std::unordered_set<std::string> c_uset {"one", "two", "three", "four", "one"};
json j_uset(c_uset); // only one entry for "one" is used
// maybe ["two", "three", "four", "one"]

std::multiset<std::string> c_mset {"one", "two", "one", "four"};
json j_mset(c_mset); // both entries for "one" are used
// maybe ["one", "two", "one", "four"]

std::unordered_multiset<std::string> c_umset {"one", "two", "one", "four"};
json j_umset(c_umset); // both entries for "one" are used
// maybe ["one", "two", "one", "four"]

同样,任何键值对容器(std::map,std::multimap,std::unordered_map,std::unordered_multimap),其键可以构造一个std::string,并且其值可以被用于构建JSON值(见上文示例)可用于创建一个JSON对象。请注意,在多映射的情况下,JSON对象中只使用一个键,值取决于STL容器的内部顺序。

std::map<std::string, int> c_map { {"one", 1}, {"two", 2}, {"three", 3} };
json j_map(c_map);
// {"one": 1, "three": 3, "two": 2 }

std::unordered_map<const char*, double> c_umap { {"one", 1.2}, {"two", 2.3}, {"three", 3.4} };
json j_umap(c_umap);
// {"one": 1.2, "two": 2.3, "three": 3.4}

std::multimap<std::string, bool> c_mmap { {"one", true}, {"two", true}, {"three", false}, {"three", true} };
json j_mmap(c_mmap); // only one entry for key "three" is used
// maybe {"one": true, "two": true, "three": true}

std::unordered_multimap<std::string, bool> c_ummap { {"one", true}, {"two", true}, {"three", false}, {"three", true} };
json j_ummap(c_ummap); // only one entry for key "three" is used
// maybe {"one": true, "two": true, "three": true}

JSON Pointer和JSON Patch

该库支持JSON PointerRFC 6901)作为处理结构化值的替代方法。最重要的是,JSON PatchRFC 6902)允许描述两个JSON值之间的差异 - 有效地允许Unix中已知的patch和diff操作。

// a JSON value
json j_original = R"({
  "baz": ["one", "two", "three"],
  "foo": "bar"
})"_json;

// access members with a JSON pointer (RFC 6901)
j_original["/baz/1"_json_pointer];
// "two"

// a JSON patch (RFC 6902)
json j_patch = R"([
  { "op": "replace", "path": "/baz", "value": "boo" },
  { "op": "add", "path": "/hello", "value": ["world"] },
  { "op": "remove", "path": "/foo"}
])"_json;

// apply the patch
json j_result = j_original.patch(j_patch);
// {
//    "baz": "boo",
//    "hello": ["world"]
// }

// calculate a JSON patch from two JSON values
json::diff(j_result, j_original);
// [
//   { "op":" replace", "path": "/baz", "value": ["one", "two", "three"] },
//   { "op": "remove","path": "/hello" },
//   { "op": "add", "path": "/foo", "value": "bar" }
// ]

JSON合并Patch

该库支持JSON Merge PatchRFC 7386)作为补丁格式。它不是使用JSON指针(参见上文)来指定要操作的值,而是使用与所修改文档非常相似的语法来描述更改。

// a JSON value
json j_document = R"({
  "a": "b",
  "c": {
    "d": "e",
    "f": "g"
  }
})"_json;

// a patch
json j_patch = R"({
  "a":"z",
  "c": {
    "f": null
  }
})"_json;

// apply the patch
j_original.merge_patch(j_patch);
// {
//  "a": "z",
//  "c": {
//    "d": "e"
//  }
// }

隐式转换
JSON对象的类型由要存储的表达式自动确定。同样,隐式转换存储的值。

// strings
std::string s1 = "Hello, world!";
json js = s1;
std::string s2 = js;

// Booleans
bool b1 = true;
json jb = b1;
bool b2 = jb;

// numbers
int i = 42;
json jn = i;
double f = jn;

// etc.

您也可以显式请求值:

std::string vs = js.get<std::string>();
bool vb = jb.get<bool>();
int vi = jn.get<int>();

// etc.

请注意char类型不能自动转换成JSON字符串,而是转换成整型数。要转换成字符串必须显式指定:

char ch = 'A';                       // ASCII value 65
json j_default = ch;                 // stores integer number 65
json j_string = std::string(1, ch);  // stores string "A"

任意类型转换

任何类型都可以用JSON序列化,而不仅仅是STL容器和标量类型。通常,您将遵循这些准绳来做事:

namespace ns {
    // a simple struct to model a person
    struct person {
        std::string name;
        std::string address;
        int age;
    };
}

ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};

// convert to JSON: copy each value into the JSON object
json j;
j["name"] = p.name;
j["address"] = p.address;
j["age"] = p.age;

// ...

// convert from JSON: copy each value from the JSON object
ns::person p {
    j["name"].get<std::string>(),
    j["address"].get<std::string>(),
    j["age"].get<int>()
};

这个代码没有问题,但是有点啰嗦,我们有一个更好的办法:

// create a person
ns::person p {"Ned Flanders", "744 Evergreen Terrace", 60};

// conversion: person -> json
json j = p;

std::cout << j << std::endl;
// {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"}

// conversion: json -> person
ns::person p2 = j;

// that's it
assert(p == p2);

基本用法

要使其适用于您的某种类型,您只需提供两个功能:

using nlohmann::json;

namespace ns {
    void to_json(json& j, const person& p) {
        j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}};
    }

    void from_json(const json& j, person& p) {
        p.name = j.at("name").get<std::string>();
        p.address = j.at("address").get<std::string>();
        p.age = j.at("age").get<int>();
    }
} // namespace ns

就这么简单!json使用您的类型调用构造函数时,您的自定义方法to_json将被自动调用。同样,在调用get<your_type>()时,from_json将被自动调用。
一些重要的点:

  • 那些方法必须在你的类型的命名空间(可以是全局命名空间)中,否则库将无法找到它们(在这个例子中,person在命名空间中ns中定义)。
  • 在您使用隐式转换的地方,那些方法必须是有效的(例如:正确的头文件必须被包含)查看问题1108,了解可能发生的错误。
  • 使用get<your_type>()时,your_type 必须DefaultConstructible。(后面会描述一种可以绕过这个要求的方法。)
  • 在函数from_json中,使用函数at()来访问对象值而不是operator[]。如果某个键不存在,则at抛出一个可以处理的异常,然而operator[]显示未定义的行为。
  • 如果您的类型包含多个operator=定义,则代码your_variable = your_json; 可能无法编译。您需要改写成your_variable = your_json.get<decltype your_variable>();
  • 您不需要为STL类型添加序列化或反序列化程序,例如std::vector:库已经实现了这些。
  • 注意函数from_json/ to_json的定义顺序:如果一个类型B有类型的成员A,你必须在定义to_json(B)之前,定义to_json(A)。请查看问题561以获取更多详细信息。

怎么转换第三方库的类型?

这需要更高级的技术。首先,让我们来看看这种转换机制是如何工作的:
该库使用JSON Serializers将类型转换成json。nlohmann::json的默认序列化程序是nlohmann::adl_serializer (ADL means Argument-Dependent Lookup)。
它是这样实现的(简化):

template <typename T>
struct adl_serializer {
    static void to_json(json& j, const T& value) {
        // calls the "to_json" method in T's namespace
    }

    static void from_json(const json& j, T& value) {
        // same thing, but with the "from_json" method
    }
};

当您控制类型的命名空间时,此序列化程序可以正常工作。但是,boost::optional或者std::filesystem::path(C ++ 17)呢?盗用boost命名空间是非常糟糕的,并且向stl中添加模板特化以外的东西是非法......
为了解决这个问题,您需要向命名空间nlohmann中添加adl_serializer,示例如下:

// partial specialization (full specialization works too)
namespace nlohmann {
   template <typename T>
   struct adl_serializer<boost::optional<T>> {
       static void to_json(json& j, const boost::optional<T>& opt) {
           if (opt == boost::none) {
               j = nullptr;
           } else {
             j = *opt; // this will call adl_serializer<T>::to_json which will
                       // find the free function to_json in T's namespace!
           }
       }

       static void from_json(const json& j, boost::optional<T>& opt) {
           if (j.is_null()) {
               opt = boost::none;
           } else {
               opt = j.get<T>(); // same as above, but with
                                 // adl_serializer<T>::from_json
           }
       }
   };
}

如何在没有默认构造函数或不可拷贝的类型使用get()

有个办法,如果您的类型是MoveConstructible的。您也将需要特化adl_serializer,重载from_json

struct move_only_type {
    move_only_type() = delete;
    move_only_type(int ii): i(ii) {}
    move_only_type(const move_only_type&) = delete;
    move_only_type(move_only_type&&) = default;

    int i;
};

namespace nlohmann {
    template <>
    struct adl_serializer<move_only_type> {
        // note: the return type is no longer 'void', and the method only takes
        // one argument
        static move_only_type from_json(const json& j) {
            return {j.get<int>()};
        }

        // Here's the catch! You must provide a to_json method! Otherwise you
        // will not be able to convert move_only_type to json, since you fully
        // specialized adl_serializer on that type
        static void to_json(json& j, move_only_type t) {
            j = t.i;
        }
    };
}

怎么编写自己的序列化程序?(高级用途)

您可以看下在测试套件unit-udt.cpp查看一些示例。
如果您编写自己的序列化程序,则需要执行以下操作:

  • basic_json,使用与 nlohmann::json中不同的别名(basic_json的最后一个模板参数是JSONSerializer
  • 在您所有的to_json/from_json函数中,使用您对basic_json起的别名(或模板参数)。
  • 当您需要ADL时,使用nlohmann::to_jsonnlohmann::from_json

下面是一个示例,只接受大小<=32的类型并且使用ADL。

// You should use void as a second template argument
// if you don't need compile-time checks on T
template<typename T, typename SFINAE = typename std::enable_if<sizeof(T) <= 32>::type>
struct less_than_32_serializer {
    template <typename BasicJsonType>
    static void to_json(BasicJsonType& j, T value) {
        // we want to use ADL, and call the correct to_json overload
        using nlohmann::to_json; // this method is called by adl_serializer,
                                 // this is where the magic happens
        to_json(j, value);
    }

    template <typename BasicJsonType>
    static void from_json(const BasicJsonType& j, T& value) {
        // same thing here
        using nlohmann::from_json;
        from_json(j, value);
    }
};

重新实现序列化程序时,要非常小心,如果不注意,可能堆栈溢出:

template <typename T, void>
struct bad_serializer
{
    template <typename BasicJsonType>
    static void to_json(BasicJsonType& j, const T& value) {
      // this calls BasicJsonType::json_serializer<T>::to_json(j, value);
      // if BasicJsonType::json_serializer == bad_serializer ... oops!
      j = value;
    }

    template <typename BasicJsonType>
    static void to_json(const BasicJsonType& j, T& value) {
      // this calls BasicJsonType::json_serializer<T>::from_json(j, value);
      // if BasicJsonType::json_serializer == bad_serializer ... oops!
      value = j.template get<T>(); // oops!
    }
};

二进制格式(CBOR, MessagePack, and UBJSON)

虽然JSON是一种普遍存在的数据格式,但它并不是一种非常紧凑的格式,适用于数据交换,例如通过网络。因为,该库支持CBOR (简明二进制对象表示),MessagePackUBJSON (通用二进制规范)以有效地将JSON值编码为字节向量和解码此类向量。

// create a JSON value
json j = R"({"compact": true, "schema": 0})"_json;

// serialize to CBOR
std::vector<std::uint8_t> v_cbor = json::to_cbor(j);

// 0xA2, 0x67, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0xF5, 0x66, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x00

// roundtrip
json j_from_cbor = json::from_cbor(v_cbor);

// serialize to MessagePack
std::vector<std::uint8_t> v_msgpack = json::to_msgpack(j);

// 0x82, 0xA7, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0xC3, 0xA6, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x00

// roundtrip
json j_from_msgpack = json::from_msgpack(v_msgpack);

// serialize to UBJSON
std::vector<std::uint8_t> v_ubjson = json::to_ubjson(j);

// 0x7B, 0x69, 0x07, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x54, 0x69, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x69, 0x00, 0x7D

// roundtrip
json j_from_ubjson = json::from_ubjson(v_ubjson);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,898评论 2 89
  • 官方中文版原文链接 感谢社区中各位的大力支持,译者再次奉上一点点福利:阿里云产品券,享受所有官网优惠,并抽取幸运大...
    HetfieldJoe阅读 2,501评论 1 17
  • 童年的梦繁星点点,阳光下的我们在成长,时光留下了我们的脚步, 光阴似箭,日月如梭。“童年”两个字渐...
    紫東阅读 294评论 0 2
  • 妈妈12点半进手术室,1点多宝宝的胎盘出来了,1点7分宝宝出生,按照时差大概1点37分出生。 爸爸抱着宝宝,宝宝左...
    miaoyin阅读 227评论 0 0