1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-10-19 11:02:59 +02:00

Lift JSON library from clang-tools-extra/clangd to llvm/Support.

Summary:
This consists of four main parts:
 - an type json::Expr representing JSON values of dynamic kind, which can be
   composed, inspected, and modified
 - a JSON parser from string -> json::Expr
 - a JSON printer from json::Expr -> string, with optional pretty-printing
 - a convention for mapping json::Expr <=> native types (fromJSON/toJSON)
   Mapping functions are provided for primitives (e.g. int, vector) and the
   ObjectMapper helper helps implement fromJSON for struct/object types.

Based on clangd's usage, a couple of places I'd appreciate review attention:
 - fromJSON returns only bool. A richer error-signaling mechanism may be useful
   to provide useful messages, or let recursive fromJSONs (containers/structs)
   do careful error recovery.
 - should json::obj be always explicitly written (like json::ary)
 - there's no streaming parse API. I suspect there are some simple wins like
   a callback API where the document is a long array, and each element is small.
   But this can probably be bolted on easily when we see the need.

Reviewers: bkramer, labath

Subscribers: mgorny, ilya-biryukov, ioeric, MaskRay, llvm-commits

Differential Revision: https://reviews.llvm.org/D45753

llvm-svn: 336534
This commit is contained in:
Sam McCall 2018-07-09 10:05:41 +00:00
parent 1db514b528
commit 43bc57d016
5 changed files with 1563 additions and 0 deletions

627
include/llvm/Support/JSON.h Normal file
View File

@ -0,0 +1,627 @@
//===--- JSON.h - JSON values, parsing and serialization -------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===---------------------------------------------------------------------===//
///
/// \file
/// This file supports working with JSON data.
///
/// It comprises:
///
/// - classes which hold dynamically-typed parsed JSON structures
/// These are value types that can be composed, inspected, and modified.
/// See json::Value, and the related types json::Object and json::Array.
///
/// - functions to parse JSON text into Values, and to serialize Values to text.
/// See parse(), operator<<, and format_provider.
///
/// - a convention and helpers for mapping between json::Value and user-defined
/// types. See fromJSON(), ObjectMapper, and the class comment on Value.
///
/// Typically, JSON data would be read from an external source, parsed into
/// a Value, and then converted into some native data structure before doing
/// real work on it. (And vice versa when writing).
///
/// Other serialization mechanisms you may consider:
///
/// - YAML is also text-based, and more human-readable than JSON. It's a more
/// complex format and data model, and YAML parsers aren't ubiquitous.
/// YAMLParser.h is a streaming parser suitable for parsing large documents
/// (including JSON, as YAML is a superset). It can be awkward to use
/// directly. YAML I/O (YAMLTraits.h) provides data mapping that is more
/// declarative than the toJSON/fromJSON conventions here.
///
/// - LLVM bitstream is a space- and CPU- efficient binary format. Typically it
/// encodes LLVM IR ("bitcode"), but it can be a container for other data.
/// Low-level reader/writer libraries are in Bitcode/Bitstream*.h
///
//===---------------------------------------------------------------------===//
#ifndef LLVM_SUPPORT_JSON_H
#define LLVM_SUPPORT_JSON_H
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_ostream.h"
#include <map>
namespace llvm {
namespace json {
class Array;
class ObjectKey;
class Value;
/// An Object is a JSON object, which maps strings to heterogenous JSON values.
/// It simulates DenseMap<ObjectKey, Value>. ObjectKey is a maybe-owned string.
class Object {
using Storage = DenseMap<ObjectKey, Value, llvm::DenseMapInfo<StringRef>>;
Storage M;
public:
using key_type = ObjectKey;
using mapped_type = Value;
using value_type = Storage::value_type;
using iterator = Storage::iterator;
using const_iterator = Storage::const_iterator;
explicit Object() = default;
// KV is a trivial key-value struct for list-initialization.
// (using std::pair forces extra copies).
struct KV;
explicit Object(std::initializer_list<KV> Properties);
iterator begin() { return M.begin(); }
const_iterator begin() const { return M.begin(); }
iterator end() { return M.end(); }
const_iterator end() const { return M.end(); }
bool empty() const { return M.empty(); }
size_t size() const { return M.size(); }
void clear() { M.clear(); }
std::pair<iterator, bool> insert(KV E);
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const ObjectKey &K, Ts &&... Args) {
return M.try_emplace(K, std::forward<Ts>(Args)...);
}
template <typename... Ts>
std::pair<iterator, bool> try_emplace(ObjectKey &&K, Ts &&... Args) {
return M.try_emplace(std::move(K), std::forward<Ts>(Args)...);
}
iterator find(StringRef K) { return M.find_as(K); }
const_iterator find(StringRef K) const { return M.find_as(K); }
// operator[] acts as if Value was default-constructible as null.
Value &operator[](const ObjectKey &K);
Value &operator[](ObjectKey &&K);
// Look up a property, returning nullptr if it doesn't exist.
Value *get(StringRef K);
const Value *get(StringRef K) const;
// Typed accessors return None/nullptr if
// - the property doesn't exist
// - or it has the wrong type
llvm::Optional<std::nullptr_t> getNull(StringRef K) const;
llvm::Optional<bool> getBoolean(StringRef K) const;
llvm::Optional<double> getNumber(StringRef K) const;
llvm::Optional<int64_t> getInteger(StringRef K) const;
llvm::Optional<llvm::StringRef> getString(StringRef K) const;
const json::Object *getObject(StringRef K) const;
json::Object *getObject(StringRef K);
const json::Array *getArray(StringRef K) const;
json::Array *getArray(StringRef K);
};
bool operator==(const Object &LHS, const Object &RHS);
inline bool operator!=(const Object &LHS, const Object &RHS) {
return !(LHS == RHS);
}
/// An Array is a JSON array, which contains heterogeneous JSON values.
/// It simulates std::vector<Value>.
class Array {
std::vector<Value> V;
public:
using value_type = Value;
using iterator = std::vector<Value>::iterator;
using const_iterator = std::vector<Value>::const_iterator;
explicit Array() = default;
explicit Array(std::initializer_list<Value> Elements);
template <typename Collection> explicit Array(const Collection &C) {
for (const auto &V : C)
emplace_back(V);
}
Value &operator[](size_t I) { return V[I]; }
const Value &operator[](size_t I) const { return V[I]; }
Value &front() { return V.front(); }
const Value &front() const { return V.front(); }
Value &back() { return V.back(); }
const Value &back() const { return V.back(); }
Value *data() { return V.data(); }
const Value *data() const { return V.data(); }
iterator begin() { return V.begin(); }
const_iterator begin() const { return V.begin(); }
iterator end() { return V.end(); }
const_iterator end() const { return V.end(); }
bool empty() const { return V.empty(); }
size_t size() const { return V.size(); }
void clear() { V.clear(); }
void push_back(const Value &E) { V.push_back(E); }
void push_back(Value &&E) { V.push_back(std::move(E)); }
template <typename... Args> void emplace_back(Args &&... A) {
V.emplace_back(std::forward<Args>(A)...);
}
void pop_back() { V.pop_back(); }
iterator insert(const_iterator P, const Value &E) { return V.insert(P, E); }
iterator insert(const_iterator P, Value &&E) {
return V.insert(P, std::move(E));
}
template <typename It> iterator insert(const_iterator P, It A, It Z) {
return V.insert(P, A, Z);
}
template <typename... Args> iterator emplace(const_iterator P, Args &&... A) {
return V.emplace(P, std::forward<Args>(A)...);
}
friend bool operator==(const Array &L, const Array &R) { return L.V == R.V; }
};
inline bool operator!=(const Array &L, const Array &R) { return !(L == R); }
/// A Value is an JSON value of unknown type.
/// They can be copied, but should generally be moved.
///
/// === Composing values ===
///
/// You can implicitly construct Values from:
/// - strings: std::string, SmallString, formatv, StringRef, char*
/// (char*, and StringRef are references, not copies!)
/// - numbers
/// - booleans
/// - null: nullptr
/// - arrays: {"foo", 42.0, false}
/// - serializable things: types with toJSON(const T&)->Value, found by ADL
///
/// They can also be constructed from object/array helpers:
/// - json::Object is a type like map<ObjectKey, Value>
/// - json::Array is a type like vector<Value>
/// These can be list-initialized, or used to build up collections in a loop.
/// json::ary(Collection) converts all items in a collection to Values.
///
/// === Inspecting values ===
///
/// Each Value is one of the JSON kinds:
/// null (nullptr_t)
/// boolean (bool)
/// number (double)
/// string (StringRef)
/// array (json::Array)
/// object (json::Object)
///
/// The kind can be queried directly, or implicitly via the typed accessors:
/// if (Optional<StringRef> S = E.getAsString()
/// assert(E.kind() == Value::String);
///
/// Array and Object also have typed indexing accessors for easy traversal:
/// Expected<Value> E = parse(R"( {"options": {"font": "sans-serif"}} )");
/// if (Object* O = E->getAsObject())
/// if (Object* Opts = O->getObject("options"))
/// if (Optional<StringRef> Font = Opts->getString("font"))
/// assert(Opts->at("font").kind() == Value::String);
///
/// === Converting JSON values to C++ types ===
///
/// The convention is to have a deserializer function findable via ADL:
/// fromJSON(const json::Value&, T&)->bool
/// Deserializers are provided for:
/// - bool
/// - int
/// - double
/// - std::string
/// - vector<T>, where T is deserializable
/// - map<string, T>, where T is deserializable
/// - Optional<T>, where T is deserializable
/// ObjectMapper can help writing fromJSON() functions for object types.
///
/// For conversion in the other direction, the serializer function is:
/// toJSON(const T&) -> json::Value
/// If this exists, then it also allows constructing Value from T, and can
/// be used to serialize vector<T>, map<string, T>, and Optional<T>.
///
/// === Serialization ===
///
/// Values can be serialized to JSON:
/// 1) raw_ostream << Value // Basic formatting.
/// 2) raw_ostream << formatv("{0}", Value) // Basic formatting.
/// 3) raw_ostream << formatv("{0:2}", Value) // Pretty-print with indent 2.
///
/// And parsed:
/// Expected<Value> E = json::parse("[1, 2, null]");
/// assert(E && E->kind() == Value::Array);
class Value {
public:
enum Kind {
Null,
Boolean,
Number,
String,
Array,
Object,
};
// It would be nice to have Value() be null. But that would make {} null too.
Value(const Value &M) { copyFrom(M); }
Value(Value &&M) { moveFrom(std::move(M)); }
Value(std::initializer_list<Value> Elements);
Value(json::Array &&Elements) : Type(T_Array) {
create<json::Array>(std::move(Elements));
}
Value(json::Object &&Properties) : Type(T_Object) {
create<json::Object>(std::move(Properties));
}
// Strings: types with value semantics.
Value(std::string &&V) : Type(T_String) { create<std::string>(std::move(V)); }
Value(const std::string &V) : Type(T_String) { create<std::string>(V); }
Value(const llvm::SmallVectorImpl<char> &V) : Type(T_String) {
create<std::string>(V.begin(), V.end());
}
Value(const llvm::formatv_object_base &V) : Value(V.str()){};
// Strings: types with reference semantics.
Value(llvm::StringRef V) : Type(T_StringRef) { create<llvm::StringRef>(V); }
Value(const char *V) : Type(T_StringRef) { create<llvm::StringRef>(V); }
Value(std::nullptr_t) : Type(T_Null) {}
// Prevent implicit conversions to boolean.
template <typename T, typename = typename std::enable_if<
std::is_same<T, bool>::value>::type>
Value(T B) : Type(T_Boolean) {
create<bool>(B);
}
// Numbers: arithmetic types that are not boolean.
template <
typename T,
typename = typename std::enable_if<std::is_arithmetic<T>::value>::type,
typename = typename std::enable_if<!std::is_same<T, bool>::value>::type>
Value(T D) : Type(T_Number) {
create<double>(D);
}
// Serializable types: with a toJSON(const T&)->Value function, found by ADL.
template <typename T,
typename = typename std::enable_if<std::is_same<
Value, decltype(toJSON(*(const T *)nullptr))>::value>>
Value(const T &V) : Value(toJSON(V)) {}
Value &operator=(const Value &M) {
destroy();
copyFrom(M);
return *this;
}
Value &operator=(Value &&M) {
destroy();
moveFrom(std::move(M));
return *this;
}
~Value() { destroy(); }
Kind kind() const {
switch (Type) {
case T_Null:
return Null;
case T_Boolean:
return Boolean;
case T_Number:
return Number;
case T_String:
case T_StringRef:
return String;
case T_Object:
return Object;
case T_Array:
return Array;
}
llvm_unreachable("Unknown kind");
}
// Typed accessors return None/nullptr if the Value is not of this type.
llvm::Optional<std::nullptr_t> getAsNull() const {
if (LLVM_LIKELY(Type == T_Null))
return nullptr;
return llvm::None;
}
llvm::Optional<bool> getAsBoolean() const {
if (LLVM_LIKELY(Type == T_Boolean))
return as<bool>();
return llvm::None;
}
llvm::Optional<double> getAsNumber() const {
if (LLVM_LIKELY(Type == T_Number))
return as<double>();
return llvm::None;
}
llvm::Optional<int64_t> getAsInteger() const {
if (LLVM_LIKELY(Type == T_Number)) {
double D = as<double>();
if (LLVM_LIKELY(std::modf(D, &D) == 0.0 &&
D >= double(std::numeric_limits<int64_t>::min()) &&
D <= double(std::numeric_limits<int64_t>::max())))
return D;
}
return llvm::None;
}
llvm::Optional<llvm::StringRef> getAsString() const {
if (Type == T_String)
return llvm::StringRef(as<std::string>());
if (LLVM_LIKELY(Type == T_StringRef))
return as<llvm::StringRef>();
return llvm::None;
}
const json::Object *getAsObject() const {
return LLVM_LIKELY(Type == T_Object) ? &as<json::Object>() : nullptr;
}
json::Object *getAsObject() {
return LLVM_LIKELY(Type == T_Object) ? &as<json::Object>() : nullptr;
}
const json::Array *getAsArray() const {
return LLVM_LIKELY(Type == T_Array) ? &as<json::Array>() : nullptr;
}
json::Array *getAsArray() {
return LLVM_LIKELY(Type == T_Array) ? &as<json::Array>() : nullptr;
}
/// Serializes this Value to JSON, writing it to the provided stream.
/// The formatting is compact (no extra whitespace) and deterministic.
/// For pretty-printing, use the formatv() format_provider below.
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Value &);
private:
void destroy();
void copyFrom(const Value &M);
// We allow moving from *const* Values, by marking all members as mutable!
// This hack is needed to support initializer-list syntax efficiently.
// (std::initializer_list<T> is a container of const T).
void moveFrom(const Value &&M);
friend class Array;
friend class Object;
template <typename T, typename... U> void create(U &&... V) {
new (reinterpret_cast<T *>(Union.buffer)) T(std::forward<U>(V)...);
}
template <typename T> T &as() const {
return *reinterpret_cast<T *>(Union.buffer);
}
template <typename Indenter>
void print(llvm::raw_ostream &, const Indenter &) const;
friend struct llvm::format_provider<llvm::json::Value>;
enum ValueType : char {
T_Null,
T_Boolean,
// FIXME: splitting Number into Double and Integer would allow us to
// round-trip 64-bit integers.
T_Number,
T_StringRef,
T_String,
T_Object,
T_Array,
};
// All members mutable, see moveFrom().
mutable ValueType Type;
mutable llvm::AlignedCharArrayUnion<bool, double, llvm::StringRef,
std::string, json::Array, json::Object>
Union;
};
bool operator==(const Value &, const Value &);
inline bool operator!=(const Value &L, const Value &R) { return !(L == R); }
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Value &);
/// ObjectKey is a used to capture keys in Object. Like Value but:
/// - only strings are allowed
/// - it's optimized for the string literal case (Owned == nullptr)
class ObjectKey {
public:
ObjectKey(const char *S) : Data(S) {}
ObjectKey(llvm::StringRef S) : Data(S) {}
ObjectKey(std::string &&V)
: Owned(new std::string(std::move(V))), Data(*Owned) {}
ObjectKey(const std::string &V) : Owned(new std::string(V)), Data(*Owned) {}
ObjectKey(const llvm::SmallVectorImpl<char> &V)
: ObjectKey(std::string(V.begin(), V.end())) {}
ObjectKey(const llvm::formatv_object_base &V) : ObjectKey(V.str()) {}
ObjectKey(const ObjectKey &C) { *this = C; }
ObjectKey(ObjectKey &&C) : ObjectKey(static_cast<const ObjectKey &&>(C)) {}
ObjectKey &operator=(const ObjectKey &C) {
if (C.Owned) {
Owned.reset(new std::string(*C.Owned));
Data = *Owned;
} else {
Data = C.Data;
}
return *this;
}
ObjectKey &operator=(ObjectKey &&) = default;
operator llvm::StringRef() const { return Data; }
std::string str() const { return Data.str(); }
private:
// FIXME: this is unneccesarily large (3 pointers). Pointer + length + owned
// could be 2 pointers at most.
std::unique_ptr<std::string> Owned;
llvm::StringRef Data;
};
inline bool operator==(const ObjectKey &L, const ObjectKey &R) {
return llvm::StringRef(L) == llvm::StringRef(R);
}
inline bool operator!=(const ObjectKey &L, const ObjectKey &R) {
return !(L == R);
}
inline bool operator<(const ObjectKey &L, const ObjectKey &R) {
return StringRef(L) < StringRef(R);
}
struct Object::KV {
ObjectKey K;
Value V;
};
inline Object::Object(std::initializer_list<KV> Properties) {
for (const auto &P : Properties) {
auto R = try_emplace(P.K, nullptr);
if (R.second)
R.first->getSecond().moveFrom(std::move(P.V));
}
}
inline std::pair<Object::iterator, bool> Object::insert(KV E) {
return try_emplace(std::move(E.K), std::move(E.V));
}
// Standard deserializers are provided for primitive types.
// See comments on Value.
inline bool fromJSON(const Value &E, std::string &Out) {
if (auto S = E.getAsString()) {
Out = *S;
return true;
}
return false;
}
inline bool fromJSON(const Value &E, int &Out) {
if (auto S = E.getAsInteger()) {
Out = *S;
return true;
}
return false;
}
inline bool fromJSON(const Value &E, double &Out) {
if (auto S = E.getAsNumber()) {
Out = *S;
return true;
}
return false;
}
inline bool fromJSON(const Value &E, bool &Out) {
if (auto S = E.getAsBoolean()) {
Out = *S;
return true;
}
return false;
}
template <typename T> bool fromJSON(const Value &E, llvm::Optional<T> &Out) {
if (E.getAsNull()) {
Out = llvm::None;
return true;
}
T Result;
if (!fromJSON(E, Result))
return false;
Out = std::move(Result);
return true;
}
template <typename T> bool fromJSON(const Value &E, std::vector<T> &Out) {
if (auto *A = E.getAsArray()) {
Out.clear();
Out.resize(A->size());
for (size_t I = 0; I < A->size(); ++I)
if (!fromJSON((*A)[I], Out[I]))
return false;
return true;
}
return false;
}
template <typename T>
bool fromJSON(const Value &E, std::map<std::string, T> &Out) {
if (auto *O = E.getAsObject()) {
Out.clear();
for (const auto &KV : *O)
if (!fromJSON(KV.second, Out[llvm::StringRef(KV.first)]))
return false;
return true;
}
return false;
}
/// Helper for mapping JSON objects onto protocol structs.
///
/// Example:
/// \code
/// bool fromJSON(const Value &E, MyStruct &R) {
/// ObjectMapper O(E);
/// if (!O || !O.map("mandatory_field", R.MandatoryField))
/// return false;
/// O.map("optional_field", R.OptionalField);
/// return true;
/// }
/// \endcode
class ObjectMapper {
public:
ObjectMapper(const Value &E) : O(E.getAsObject()) {}
/// True if the expression is an object.
/// Must be checked before calling map().
operator bool() { return O; }
/// Maps a property to a field, if it exists.
template <typename T> bool map(StringRef Prop, T &Out) {
assert(*this && "Must check this is an object before calling map()");
if (const Value *E = O->get(Prop))
return fromJSON(*E, Out);
return false;
}
/// Maps a property to a field, if it exists.
/// (Optional requires special handling, because missing keys are OK).
template <typename T> bool map(StringRef Prop, llvm::Optional<T> &Out) {
assert(*this && "Must check this is an object before calling map()");
if (const Value *E = O->get(Prop))
return fromJSON(*E, Out);
Out = llvm::None;
return true;
}
private:
const Object *O;
};
/// Parses the provided JSON source, or returns a ParseError.
/// The returned Value is self-contained and owns its strings (they do not refer
/// to the original source).
llvm::Expected<Value> parse(llvm::StringRef JSON);
class ParseError : public llvm::ErrorInfo<ParseError> {
const char *Msg;
unsigned Line, Column, Offset;
public:
static char ID;
ParseError(const char *Msg, unsigned Line, unsigned Column, unsigned Offset)
: Msg(Msg), Line(Line), Column(Column), Offset(Offset) {}
void log(llvm::raw_ostream &OS) const override {
OS << llvm::formatv("[{0}:{1}, byte={2}]: {3}", Line, Column, Offset, Msg);
}
std::error_code convertToErrorCode() const override {
return llvm::inconvertibleErrorCode();
}
};
} // namespace json
/// Allow printing json::Value with formatv().
/// The default style is basic/compact formatting, like operator<<.
/// A format string like formatv("{0:2}", Value) pretty-prints with indent 2.
template <> struct format_provider<llvm::json::Value> {
static void format(const llvm::json::Value &, raw_ostream &, StringRef);
};
} // namespace llvm
#endif

View File

@ -83,6 +83,7 @@ add_llvm_library(LLVMSupport
IntEqClasses.cpp
IntervalMap.cpp
JamCRC.cpp
JSON.cpp
KnownBits.cpp
LEB128.cpp
LineIterator.cpp

642
lib/Support/JSON.cpp Normal file
View File

@ -0,0 +1,642 @@
//=== JSON.cpp - JSON value, parsing and serialization - C++ -----------*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===---------------------------------------------------------------------===//
#include "llvm/Support/JSON.h"
#include "llvm/Support/Format.h"
#include <cctype>
namespace llvm {
namespace json {
Value &Object::operator[](const ObjectKey &K) {
return try_emplace(K, nullptr).first->getSecond();
}
Value &Object::operator[](ObjectKey &&K) {
return try_emplace(std::move(K), nullptr).first->getSecond();
}
Value *Object::get(StringRef K) {
auto I = find(K);
if (I == end())
return nullptr;
return &I->second;
}
const Value *Object::get(StringRef K) const {
auto I = find(K);
if (I == end())
return nullptr;
return &I->second;
}
llvm::Optional<std::nullptr_t> Object::getNull(StringRef K) const {
if (auto *V = get(K))
return V->getAsNull();
return llvm::None;
}
llvm::Optional<bool> Object::getBoolean(StringRef K) const {
if (auto *V = get(K))
return V->getAsBoolean();
return llvm::None;
}
llvm::Optional<double> Object::getNumber(StringRef K) const {
if (auto *V = get(K))
return V->getAsNumber();
return llvm::None;
}
llvm::Optional<int64_t> Object::getInteger(StringRef K) const {
if (auto *V = get(K))
return V->getAsInteger();
return llvm::None;
}
llvm::Optional<llvm::StringRef> Object::getString(StringRef K) const {
if (auto *V = get(K))
return V->getAsString();
return llvm::None;
}
const json::Object *Object::getObject(StringRef K) const {
if (auto *V = get(K))
return V->getAsObject();
return nullptr;
}
json::Object *Object::getObject(StringRef K) {
if (auto *V = get(K))
return V->getAsObject();
return nullptr;
}
const json::Array *Object::getArray(StringRef K) const {
if (auto *V = get(K))
return V->getAsArray();
return nullptr;
}
json::Array *Object::getArray(StringRef K) {
if (auto *V = get(K))
return V->getAsArray();
return nullptr;
}
bool operator==(const Object &LHS, const Object &RHS) {
if (LHS.size() != RHS.size())
return false;
for (const auto &L : LHS) {
auto R = RHS.find(L.first);
if (R == RHS.end() || L.second != R->second)
return false;
}
return true;
}
Array::Array(std::initializer_list<Value> Elements) {
V.reserve(Elements.size());
for (const Value &V : Elements) {
emplace_back(nullptr);
back().moveFrom(std::move(V));
}
}
Value::Value(std::initializer_list<Value> Elements)
: Value(json::Array(Elements)) {}
void Value::copyFrom(const Value &M) {
Type = M.Type;
switch (Type) {
case T_Null:
case T_Boolean:
case T_Number:
memcpy(Union.buffer, M.Union.buffer, sizeof(Union.buffer));
break;
case T_StringRef:
create<StringRef>(M.as<StringRef>());
break;
case T_String:
create<std::string>(M.as<std::string>());
break;
case T_Object:
create<json::Object>(M.as<json::Object>());
break;
case T_Array:
create<json::Array>(M.as<json::Array>());
break;
}
}
void Value::moveFrom(const Value &&M) {
Type = M.Type;
switch (Type) {
case T_Null:
case T_Boolean:
case T_Number:
memcpy(Union.buffer, M.Union.buffer, sizeof(Union.buffer));
break;
case T_StringRef:
create<StringRef>(M.as<StringRef>());
break;
case T_String:
create<std::string>(std::move(M.as<std::string>()));
M.Type = T_Null;
break;
case T_Object:
create<json::Object>(std::move(M.as<json::Object>()));
M.Type = T_Null;
break;
case T_Array:
create<json::Array>(std::move(M.as<json::Array>()));
M.Type = T_Null;
break;
}
}
void Value::destroy() {
switch (Type) {
case T_Null:
case T_Boolean:
case T_Number:
break;
case T_StringRef:
as<StringRef>().~StringRef();
break;
case T_String:
as<std::string>().~basic_string();
break;
case T_Object:
as<json::Object>().~Object();
break;
case T_Array:
as<json::Array>().~Array();
break;
}
}
bool operator==(const Value &L, const Value &R) {
if (L.kind() != R.kind())
return false;
switch (L.kind()) {
case Value::Null:
return *L.getAsNull() == *R.getAsNull();
case Value::Boolean:
return *L.getAsBoolean() == *R.getAsBoolean();
case Value::Number:
return *L.getAsNumber() == *R.getAsNumber();
case Value::String:
return *L.getAsString() == *R.getAsString();
case Value::Array:
return *L.getAsArray() == *R.getAsArray();
case Value::Object:
return *L.getAsObject() == *R.getAsObject();
}
llvm_unreachable("Unknown value kind");
}
namespace {
// Simple recursive-descent JSON parser.
class Parser {
public:
Parser(StringRef JSON)
: Start(JSON.begin()), P(JSON.begin()), End(JSON.end()) {}
bool parseValue(Value &Out);
bool assertEnd() {
eatWhitespace();
if (P == End)
return true;
return parseError("Text after end of document");
}
Error takeError() {
assert(Err);
return std::move(*Err);
}
private:
void eatWhitespace() {
while (P != End && (*P == ' ' || *P == '\r' || *P == '\n' || *P == '\t'))
++P;
}
// On invalid syntax, parseX() functions return false and set Err.
bool parseNumber(char First, double &Out);
bool parseString(std::string &Out);
bool parseUnicode(std::string &Out);
bool parseError(const char *Msg); // always returns false
char next() { return P == End ? 0 : *P++; }
char peek() { return P == End ? 0 : *P; }
static bool isNumber(char C) {
return C == '0' || C == '1' || C == '2' || C == '3' || C == '4' ||
C == '5' || C == '6' || C == '7' || C == '8' || C == '9' ||
C == 'e' || C == 'E' || C == '+' || C == '-' || C == '.';
}
Optional<Error> Err;
const char *Start, *P, *End;
};
bool Parser::parseValue(Value &Out) {
eatWhitespace();
if (P == End)
return parseError("Unexpected EOF");
switch (char C = next()) {
// Bare null/true/false are easy - first char identifies them.
case 'n':
Out = nullptr;
return (next() == 'u' && next() == 'l' && next() == 'l') ||
parseError("Invalid JSON value (null?)");
case 't':
Out = true;
return (next() == 'r' && next() == 'u' && next() == 'e') ||
parseError("Invalid JSON value (true?)");
case 'f':
Out = false;
return (next() == 'a' && next() == 'l' && next() == 's' && next() == 'e') ||
parseError("Invalid JSON value (false?)");
case '"': {
std::string S;
if (parseString(S)) {
Out = std::move(S);
return true;
}
return false;
}
case '[': {
Out = Array{};
Array &A = *Out.getAsArray();
eatWhitespace();
if (peek() == ']') {
++P;
return true;
}
for (;;) {
A.emplace_back(nullptr);
if (!parseValue(A.back()))
return false;
eatWhitespace();
switch (next()) {
case ',':
eatWhitespace();
continue;
case ']':
return true;
default:
return parseError("Expected , or ] after array element");
}
}
}
case '{': {
Out = Object{};
Object &O = *Out.getAsObject();
eatWhitespace();
if (peek() == '}') {
++P;
return true;
}
for (;;) {
if (next() != '"')
return parseError("Expected object key");
std::string K;
if (!parseString(K))
return false;
eatWhitespace();
if (next() != ':')
return parseError("Expected : after object key");
eatWhitespace();
if (!parseValue(O[std::move(K)]))
return false;
eatWhitespace();
switch (next()) {
case ',':
eatWhitespace();
continue;
case '}':
return true;
default:
return parseError("Expected , or } after object property");
}
}
}
default:
if (isNumber(C)) {
double Num;
if (parseNumber(C, Num)) {
Out = Num;
return true;
} else {
return false;
}
}
return parseError("Invalid JSON value");
}
}
bool Parser::parseNumber(char First, double &Out) {
SmallString<24> S;
S.push_back(First);
while (isNumber(peek()))
S.push_back(next());
char *End;
Out = std::strtod(S.c_str(), &End);
return End == S.end() || parseError("Invalid JSON value (number?)");
}
bool Parser::parseString(std::string &Out) {
// leading quote was already consumed.
for (char C = next(); C != '"'; C = next()) {
if (LLVM_UNLIKELY(P == End))
return parseError("Unterminated string");
if (LLVM_UNLIKELY((C & 0x1f) == C))
return parseError("Control character in string");
if (LLVM_LIKELY(C != '\\')) {
Out.push_back(C);
continue;
}
// Handle escape sequence.
switch (C = next()) {
case '"':
case '\\':
case '/':
Out.push_back(C);
break;
case 'b':
Out.push_back('\b');
break;
case 'f':
Out.push_back('\f');
break;
case 'n':
Out.push_back('\n');
break;
case 'r':
Out.push_back('\r');
break;
case 't':
Out.push_back('\t');
break;
case 'u':
if (!parseUnicode(Out))
return false;
break;
default:
return parseError("Invalid escape sequence");
}
}
return true;
}
static void encodeUtf8(uint32_t Rune, std::string &Out) {
if (Rune < 0x80) {
Out.push_back(Rune & 0x7F);
} else if (Rune < 0x800) {
uint8_t FirstByte = 0xC0 | ((Rune & 0x7C0) >> 6);
uint8_t SecondByte = 0x80 | (Rune & 0x3F);
Out.push_back(FirstByte);
Out.push_back(SecondByte);
} else if (Rune < 0x10000) {
uint8_t FirstByte = 0xE0 | ((Rune & 0xF000) >> 12);
uint8_t SecondByte = 0x80 | ((Rune & 0xFC0) >> 6);
uint8_t ThirdByte = 0x80 | (Rune & 0x3F);
Out.push_back(FirstByte);
Out.push_back(SecondByte);
Out.push_back(ThirdByte);
} else if (Rune < 0x110000) {
uint8_t FirstByte = 0xF0 | ((Rune & 0x1F0000) >> 18);
uint8_t SecondByte = 0x80 | ((Rune & 0x3F000) >> 12);
uint8_t ThirdByte = 0x80 | ((Rune & 0xFC0) >> 6);
uint8_t FourthByte = 0x80 | (Rune & 0x3F);
Out.push_back(FirstByte);
Out.push_back(SecondByte);
Out.push_back(ThirdByte);
Out.push_back(FourthByte);
} else {
llvm_unreachable("Invalid codepoint");
}
}
// Parse a UTF-16 \uNNNN escape sequence. "\u" has already been consumed.
// May parse several sequential escapes to ensure proper surrogate handling.
// We do not use ConvertUTF.h, it can't accept and replace unpaired surrogates.
// These are invalid Unicode but valid JSON (RFC 8259, section 8.2).
bool Parser::parseUnicode(std::string &Out) {
// Invalid UTF is not a JSON error (RFC 8529§8.2). It gets replaced by U+FFFD.
auto Invalid = [&] { Out.append(/* UTF-8 */ {'\xef', '\xbf', '\xbd'}); };
// Decodes 4 hex digits from the stream into Out, returns false on error.
auto Parse4Hex = [this](uint16_t &Out) -> bool {
Out = 0;
char Bytes[] = {next(), next(), next(), next()};
for (unsigned char C : Bytes) {
if (!std::isxdigit(C))
return parseError("Invalid \\u escape sequence");
Out <<= 4;
Out |= (C > '9') ? (C & ~0x20) - 'A' + 10 : (C - '0');
}
return true;
};
uint16_t First; // UTF-16 code unit from the first \u escape.
if (!Parse4Hex(First))
return false;
// We loop to allow proper surrogate-pair error handling.
while (true) {
// Case 1: the UTF-16 code unit is already a codepoint in the BMP.
if (LLVM_LIKELY(First < 0xD800 || First >= 0xE000)) {
encodeUtf8(First, Out);
return true;
}
// Case 2: it's an (unpaired) trailing surrogate.
if (LLVM_UNLIKELY(First >= 0xDC00)) {
Invalid();
return true;
}
// Case 3: it's a leading surrogate. We expect a trailing one next.
// Case 3a: there's no trailing \u escape. Don't advance in the stream.
if (!LLVM_LIKELY(P + 2 <= End && *P == '\\' && *(P + 1) == 'u')) {
Invalid(); // Leading surrogate was unpaired.
return true;
}
P += 2;
uint16_t Second;
if (!Parse4Hex(Second))
return false;
// Case 3b: there was another \u escape, but it wasn't a trailing surrogate.
if (LLVM_UNLIKELY(Second < 0xDC00 || Second >= 0xE000)) {
Invalid(); // Leading surrogate was unpaired.
First = Second; // Second escape still needs to be processed.
continue;
}
// Case 3c: a valid surrogate pair encoding an astral codepoint.
encodeUtf8(0x10000 | ((First - 0xD800) << 10) | (Second - 0xDC00), Out);
return true;
}
}
bool Parser::parseError(const char *Msg) {
int Line = 1;
const char *StartOfLine = Start;
for (const char *X = Start; X < P; ++X) {
if (*X == 0x0A) {
++Line;
StartOfLine = X + 1;
}
}
Err.emplace(
llvm::make_unique<ParseError>(Msg, Line, P - StartOfLine, P - Start));
return false;
}
} // namespace
Expected<Value> parse(StringRef JSON) {
Parser P(JSON);
Value E = nullptr;
if (P.parseValue(E))
if (P.assertEnd())
return std::move(E);
return P.takeError();
}
char ParseError::ID = 0;
static std::vector<const Object::value_type *> sortedElements(const Object &O) {
std::vector<const Object::value_type *> Elements;
for (const auto &E : O)
Elements.push_back(&E);
llvm::sort(Elements.begin(), Elements.end(),
[](const Object::value_type *L, const Object::value_type *R) {
return L->first < R->first;
});
return Elements;
}
} // namespace json
} // namespace llvm
static void quote(llvm::raw_ostream &OS, llvm::StringRef S) {
OS << '\"';
for (unsigned char C : S) {
if (C == 0x22 || C == 0x5C)
OS << '\\';
if (C >= 0x20) {
OS << C;
continue;
}
OS << '\\';
switch (C) {
// A few characters are common enough to make short escapes worthwhile.
case '\t':
OS << 't';
break;
case '\n':
OS << 'n';
break;
case '\r':
OS << 'r';
break;
default:
OS << 'u';
llvm::write_hex(OS, C, llvm::HexPrintStyle::Lower, 4);
break;
}
}
OS << '\"';
}
enum IndenterAction {
Indent,
Outdent,
Newline,
Space,
};
// Prints JSON. The indenter can be used to control formatting.
template <typename Indenter>
void llvm::json::Value::print(raw_ostream &OS, const Indenter &I) const {
switch (Type) {
case T_Null:
OS << "null";
break;
case T_Boolean:
OS << (as<bool>() ? "true" : "false");
break;
case T_Number:
OS << format("%g", as<double>());
break;
case T_StringRef:
quote(OS, as<StringRef>());
break;
case T_String:
quote(OS, as<std::string>());
break;
case T_Object: {
bool Comma = false;
OS << '{';
I(Indent);
for (const auto *P : sortedElements(as<json::Object>())) {
if (Comma)
OS << ',';
Comma = true;
I(Newline);
quote(OS, P->first);
OS << ':';
I(Space);
P->second.print(OS, I);
}
I(Outdent);
if (Comma)
I(Newline);
OS << '}';
break;
}
case T_Array: {
bool Comma = false;
OS << '[';
I(Indent);
for (const auto &E : as<json::Array>()) {
if (Comma)
OS << ',';
Comma = true;
I(Newline);
E.print(OS, I);
}
I(Outdent);
if (Comma)
I(Newline);
OS << ']';
break;
}
}
}
void llvm::format_provider<llvm::json::Value>::format(
const llvm::json::Value &E, raw_ostream &OS, StringRef Options) {
if (Options.empty()) {
OS << E;
return;
}
unsigned IndentAmount = 0;
if (Options.getAsInteger(/*Radix=*/10, IndentAmount))
llvm_unreachable("json::Value format options should be an integer");
unsigned IndentLevel = 0;
E.print(OS, [&](IndenterAction A) {
switch (A) {
case Newline:
OS << '\n';
OS.indent(IndentLevel);
break;
case Space:
OS << ' ';
break;
case Indent:
IndentLevel += IndentAmount;
break;
case Outdent:
IndentLevel -= IndentAmount;
break;
};
});
}
llvm::raw_ostream &llvm::json::operator<<(raw_ostream &OS, const Value &E) {
E.print(OS, [](IndenterAction A) { /*ignore*/ });
return OS;
}

View File

@ -30,6 +30,7 @@ add_llvm_unittest(SupportTests
FormatVariadicTest.cpp
GlobPatternTest.cpp
Host.cpp
JSONTest.cpp
LEB128Test.cpp
LineIteratorTest.cpp
LockFileManagerTest.cpp

View File

@ -0,0 +1,292 @@
//===-- JSONTest.cpp - JSON unit tests --------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "llvm/Support/JSON.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace llvm {
namespace json {
namespace {
std::string s(const Value &E) { return llvm::formatv("{0}", E).str(); }
std::string sp(const Value &E) { return llvm::formatv("{0:2}", E).str(); }
TEST(JSONTest, Types) {
EXPECT_EQ("true", s(true));
EXPECT_EQ("null", s(nullptr));
EXPECT_EQ("2.5", s(2.5));
EXPECT_EQ(R"("foo")", s("foo"));
EXPECT_EQ("[1,2,3]", s({1, 2, 3}));
EXPECT_EQ(R"({"x":10,"y":20})", s(Object{{"x", 10}, {"y", 20}}));
}
TEST(JSONTest, Constructors) {
// Lots of edge cases around empty and singleton init lists.
EXPECT_EQ("[[[3]]]", s({{{3}}}));
EXPECT_EQ("[[[]]]", s({{{}}}));
EXPECT_EQ("[[{}]]", s({{Object{}}}));
EXPECT_EQ(R"({"A":{"B":{}}})", s(Object{{"A", Object{{"B", Object{}}}}}));
EXPECT_EQ(R"({"A":{"B":{"X":"Y"}}})",
s(Object{{"A", Object{{"B", Object{{"X", "Y"}}}}}}));
}
TEST(JSONTest, StringOwnership) {
char X[] = "Hello";
Value Alias = static_cast<const char *>(X);
X[1] = 'a';
EXPECT_EQ(R"("Hallo")", s(Alias));
std::string Y = "Hello";
Value Copy = Y;
Y[1] = 'a';
EXPECT_EQ(R"("Hello")", s(Copy));
}
TEST(JSONTest, CanonicalOutput) {
// Objects are sorted (but arrays aren't)!
EXPECT_EQ(R"({"a":1,"b":2,"c":3})", s(Object{{"a", 1}, {"c", 3}, {"b", 2}}));
EXPECT_EQ(R"(["a","c","b"])", s({"a", "c", "b"}));
EXPECT_EQ("3", s(3.0));
}
TEST(JSONTest, Escaping) {
std::string test = {
0, // Strings may contain nulls.
'\b', '\f', // Have mnemonics, but we escape numerically.
'\r', '\n', '\t', // Escaped with mnemonics.
'S', '\"', '\\', // Printable ASCII characters.
'\x7f', // Delete is not escaped.
'\xce', '\x94', // Non-ASCII UTF-8 is not escaped.
};
std::string teststring = R"("\u0000\u0008\u000c\r\n\tS\"\\)"
"\x7f\xCE\x94\"";
EXPECT_EQ(teststring, s(test));
EXPECT_EQ(R"({"object keys are\nescaped":true})",
s(Object{{"object keys are\nescaped", true}}));
}
TEST(JSONTest, PrettyPrinting) {
const char str[] = R"({
"empty_array": [],
"empty_object": {},
"full_array": [
1,
null
],
"full_object": {
"nested_array": [
{
"property": "value"
}
]
}
})";
EXPECT_EQ(str, sp(Object{
{"empty_object", Object{}},
{"empty_array", {}},
{"full_array", {1, nullptr}},
{"full_object",
Object{
{"nested_array",
{Object{
{"property", "value"},
}}},
}},
}));
}
TEST(JSONTest, Parse) {
auto Compare = [](llvm::StringRef S, Value Expected) {
if (auto E = parse(S)) {
// Compare both string forms and with operator==, in case we have bugs.
EXPECT_EQ(*E, Expected);
EXPECT_EQ(sp(*E), sp(Expected));
} else {
handleAllErrors(E.takeError(), [S](const llvm::ErrorInfoBase &E) {
FAIL() << "Failed to parse JSON >>> " << S << " <<<: " << E.message();
});
}
};
Compare(R"(true)", true);
Compare(R"(false)", false);
Compare(R"(null)", nullptr);
Compare(R"(42)", 42);
Compare(R"(2.5)", 2.5);
Compare(R"(2e50)", 2e50);
Compare(R"(1.2e3456789)", std::numeric_limits<double>::infinity());
Compare(R"("foo")", "foo");
Compare(R"("\"\\\b\f\n\r\t")", "\"\\\b\f\n\r\t");
Compare(R"("\u0000")", llvm::StringRef("\0", 1));
Compare("\"\x7f\"", "\x7f");
Compare(R"("\ud801\udc37")", u8"\U00010437"); // UTF16 surrogate pair escape.
Compare("\"\xE2\x82\xAC\xF0\x9D\x84\x9E\"", u8"\u20ac\U0001d11e"); // UTF8
Compare(
R"("LoneLeading=\ud801, LoneTrailing=\udc01, LeadingLeadingTrailing=\ud801\ud801\udc37")",
u8"LoneLeading=\ufffd, LoneTrailing=\ufffd, "
u8"LeadingLeadingTrailing=\ufffd\U00010437"); // Invalid unicode.
Compare(R"({"":0,"":0})", Object{{"", 0}});
Compare(R"({"obj":{},"arr":[]})", Object{{"obj", Object{}}, {"arr", {}}});
Compare(R"({"\n":{"\u0000":[[[[]]]]}})",
Object{{"\n", Object{
{llvm::StringRef("\0", 1), {{{{}}}}},
}}});
Compare("\r[\n\t] ", {});
}
TEST(JSONTest, ParseErrors) {
auto ExpectErr = [](llvm::StringRef Msg, llvm::StringRef S) {
if (auto E = parse(S)) {
// Compare both string forms and with operator==, in case we have bugs.
FAIL() << "Parsed JSON >>> " << S << " <<< but wanted error: " << Msg;
} else {
handleAllErrors(E.takeError(), [S, Msg](const llvm::ErrorInfoBase &E) {
EXPECT_THAT(E.message(), testing::HasSubstr(Msg)) << S;
});
}
};
ExpectErr("Unexpected EOF", "");
ExpectErr("Unexpected EOF", "[");
ExpectErr("Text after end of document", "[][]");
ExpectErr("Invalid JSON value (false?)", "fuzzy");
ExpectErr("Expected , or ]", "[2?]");
ExpectErr("Expected object key", "{a:2}");
ExpectErr("Expected : after object key", R"({"a",2})");
ExpectErr("Expected , or } after object property", R"({"a":2 "b":3})");
ExpectErr("Invalid JSON value", R"([&%!])");
ExpectErr("Invalid JSON value (number?)", "1e1.0");
ExpectErr("Unterminated string", R"("abc\"def)");
ExpectErr("Control character in string", "\"abc\ndef\"");
ExpectErr("Invalid escape sequence", R"("\030")");
ExpectErr("Invalid \\u escape sequence", R"("\usuck")");
ExpectErr("[3:3, byte=19]", R"({
"valid": 1,
invalid: 2
})");
}
TEST(JSONTest, Inspection) {
llvm::Expected<Value> Doc = parse(R"(
{
"null": null,
"boolean": false,
"number": 2.78,
"string": "json",
"array": [null, true, 3.14, "hello", [1,2,3], {"time": "arrow"}],
"object": {"fruit": "banana"}
}
)");
EXPECT_TRUE(!!Doc);
Object *O = Doc->getAsObject();
ASSERT_TRUE(O);
EXPECT_FALSE(O->getNull("missing"));
EXPECT_FALSE(O->getNull("boolean"));
EXPECT_TRUE(O->getNull("null"));
EXPECT_EQ(O->getNumber("number"), llvm::Optional<double>(2.78));
EXPECT_FALSE(O->getInteger("number"));
EXPECT_EQ(O->getString("string"), llvm::Optional<llvm::StringRef>("json"));
ASSERT_FALSE(O->getObject("missing"));
ASSERT_FALSE(O->getObject("array"));
ASSERT_TRUE(O->getObject("object"));
EXPECT_EQ(*O->getObject("object"), (Object{{"fruit", "banana"}}));
Array *A = O->getArray("array");
ASSERT_TRUE(A);
EXPECT_EQ((*A)[1].getAsBoolean(), llvm::Optional<bool>(true));
ASSERT_TRUE((*A)[4].getAsArray());
EXPECT_EQ(*(*A)[4].getAsArray(), (Array{1, 2, 3}));
EXPECT_EQ((*(*A)[4].getAsArray())[1].getAsInteger(),
llvm::Optional<int64_t>(2));
int I = 0;
for (Value &E : *A) {
if (I++ == 5) {
ASSERT_TRUE(E.getAsObject());
EXPECT_EQ(E.getAsObject()->getString("time"),
llvm::Optional<llvm::StringRef>("arrow"));
} else
EXPECT_FALSE(E.getAsObject());
}
}
// Sample struct with typical JSON-mapping rules.
struct CustomStruct {
CustomStruct() : B(false) {}
CustomStruct(std::string S, llvm::Optional<int> I, bool B)
: S(S), I(I), B(B) {}
std::string S;
llvm::Optional<int> I;
bool B;
};
inline bool operator==(const CustomStruct &L, const CustomStruct &R) {
return L.S == R.S && L.I == R.I && L.B == R.B;
}
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
const CustomStruct &S) {
return OS << "(" << S.S << ", " << (S.I ? std::to_string(*S.I) : "None")
<< ", " << S.B << ")";
}
bool fromJSON(const Value &E, CustomStruct &R) {
ObjectMapper O(E);
if (!O || !O.map("str", R.S) || !O.map("int", R.I))
return false;
O.map("bool", R.B);
return true;
}
TEST(JSONTest, Deserialize) {
std::map<std::string, std::vector<CustomStruct>> R;
CustomStruct ExpectedStruct = {"foo", 42, true};
std::map<std::string, std::vector<CustomStruct>> Expected;
Value J = Object{
{"foo",
Array{
Object{
{"str", "foo"},
{"int", 42},
{"bool", true},
{"unknown", "ignored"},
},
Object{{"str", "bar"}},
Object{
{"str", "baz"}, {"bool", "string"}, // OK, deserialize ignores.
},
}}};
Expected["foo"] = {
CustomStruct("foo", 42, true),
CustomStruct("bar", llvm::None, false),
CustomStruct("baz", llvm::None, false),
};
ASSERT_TRUE(fromJSON(J, R));
EXPECT_EQ(R, Expected);
CustomStruct V;
EXPECT_FALSE(fromJSON(nullptr, V)) << "Not an object " << V;
EXPECT_FALSE(fromJSON(Object{}, V)) << "Missing required field " << V;
EXPECT_FALSE(fromJSON(Object{{"str", 1}}, V)) << "Wrong type " << V;
// Optional<T> must parse as the correct type if present.
EXPECT_FALSE(fromJSON(Object{{"str", 1}, {"int", "string"}}, V))
<< "Wrong type for Optional<T> " << V;
}
} // namespace
} // namespace json
} // namespace llvm