// Copyright (c) From https://github.com/nbsdx/SimpleJSON
|
|
// 2022 Binbin Zhang (binbzha@qq.com)
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#ifndef UTILS_JSON_H_
|
|
#define UTILS_JSON_H_
|
|
|
|
#include <cctype>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <deque>
|
|
#include <initializer_list>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <ostream>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
namespace json {
|
|
|
|
using std::deque;
|
|
using std::enable_if;
|
|
using std::initializer_list;
|
|
using std::is_convertible;
|
|
using std::is_floating_point;
|
|
using std::is_integral;
|
|
using std::is_same;
|
|
using std::map;
|
|
using std::string;
|
|
|
|
namespace { // NOLINT
|
|
string json_escape(const string& str) {
|
|
string output;
|
|
for (unsigned i = 0; i < str.length(); ++i) switch (str[i]) {
|
|
case '\"':
|
|
output += "\\\"";
|
|
break;
|
|
case '\\':
|
|
output += "\\\\";
|
|
break;
|
|
case '\b':
|
|
output += "\\b";
|
|
break;
|
|
case '\f':
|
|
output += "\\f";
|
|
break;
|
|
case '\n':
|
|
output += "\\n";
|
|
break;
|
|
case '\r':
|
|
output += "\\r";
|
|
break;
|
|
case '\t':
|
|
output += "\\t";
|
|
break;
|
|
default:
|
|
output += str[i];
|
|
break;
|
|
}
|
|
return std::move(output);
|
|
}
|
|
} // namespace
|
|
|
|
class JSON {
|
|
union BackingData {
|
|
BackingData(double d) : Float(d) {}
|
|
BackingData(int l) : Int(l) {}
|
|
BackingData(bool b) : Bool(b) {}
|
|
BackingData(string s) : String(new string(s)) {}
|
|
BackingData() : Int(0) {}
|
|
|
|
deque<JSON>* List;
|
|
map<string, JSON>* Map;
|
|
string* String;
|
|
double Float;
|
|
int Int;
|
|
bool Bool;
|
|
} Internal;
|
|
|
|
public:
|
|
enum class Class { Null, Object, Array, String, Floating, Integral, Boolean };
|
|
|
|
template <typename Container>
|
|
class JSONWrapper {
|
|
Container* object;
|
|
|
|
public:
|
|
explicit JSONWrapper(Container* val) : object(val) {}
|
|
explicit JSONWrapper(std::nullptr_t) : object(nullptr) {}
|
|
|
|
typename Container::iterator begin() {
|
|
return object ? object->begin() : typename Container::iterator();
|
|
}
|
|
typename Container::iterator end() {
|
|
return object ? object->end() : typename Container::iterator();
|
|
}
|
|
typename Container::const_iterator begin() const {
|
|
return object ? object->begin() : typename Container::iterator();
|
|
}
|
|
typename Container::const_iterator end() const {
|
|
return object ? object->end() : typename Container::iterator();
|
|
}
|
|
};
|
|
|
|
template <typename Container>
|
|
class JSONConstWrapper {
|
|
const Container* object;
|
|
|
|
public:
|
|
explicit JSONConstWrapper(const Container* val) : object(val) {}
|
|
explicit JSONConstWrapper(std::nullptr_t) : object(nullptr) {}
|
|
|
|
typename Container::const_iterator begin() const {
|
|
return object ? object->begin() : typename Container::const_iterator();
|
|
}
|
|
typename Container::const_iterator end() const {
|
|
return object ? object->end() : typename Container::const_iterator();
|
|
}
|
|
};
|
|
|
|
JSON() : Internal(), Type(Class::Null) {}
|
|
|
|
explicit JSON(initializer_list<JSON> list) : JSON() {
|
|
SetType(Class::Object);
|
|
for (auto i = list.begin(), e = list.end(); i != e; ++i, ++i)
|
|
operator[](i->ToString()) = *std::next(i);
|
|
}
|
|
|
|
JSON(JSON&& other) : Internal(other.Internal), Type(other.Type) {
|
|
other.Type = Class::Null;
|
|
other.Internal.Map = nullptr;
|
|
}
|
|
|
|
JSON& operator=(JSON&& other) {
|
|
ClearInternal();
|
|
Internal = other.Internal;
|
|
Type = other.Type;
|
|
other.Internal.Map = nullptr;
|
|
other.Type = Class::Null;
|
|
return *this;
|
|
}
|
|
|
|
JSON(const JSON& other) {
|
|
switch (other.Type) {
|
|
case Class::Object:
|
|
Internal.Map = new map<string, JSON>(other.Internal.Map->begin(),
|
|
other.Internal.Map->end());
|
|
break;
|
|
case Class::Array:
|
|
Internal.List = new deque<JSON>(other.Internal.List->begin(),
|
|
other.Internal.List->end());
|
|
break;
|
|
case Class::String:
|
|
Internal.String = new string(*other.Internal.String);
|
|
break;
|
|
default:
|
|
Internal = other.Internal;
|
|
}
|
|
Type = other.Type;
|
|
}
|
|
|
|
JSON& operator=(const JSON& other) {
|
|
ClearInternal();
|
|
switch (other.Type) {
|
|
case Class::Object:
|
|
Internal.Map = new map<string, JSON>(other.Internal.Map->begin(),
|
|
other.Internal.Map->end());
|
|
break;
|
|
case Class::Array:
|
|
Internal.List = new deque<JSON>(other.Internal.List->begin(),
|
|
other.Internal.List->end());
|
|
break;
|
|
case Class::String:
|
|
Internal.String = new string(*other.Internal.String);
|
|
break;
|
|
default:
|
|
Internal = other.Internal;
|
|
}
|
|
Type = other.Type;
|
|
return *this;
|
|
}
|
|
|
|
~JSON() {
|
|
switch (Type) {
|
|
case Class::Array:
|
|
delete Internal.List;
|
|
break;
|
|
case Class::Object:
|
|
delete Internal.Map;
|
|
break;
|
|
case Class::String:
|
|
delete Internal.String;
|
|
break;
|
|
default: {
|
|
};
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
explicit JSON(T b, typename enable_if<is_same<T, bool>::value>::type* = 0)
|
|
: Internal(b), Type(Class::Boolean) {}
|
|
|
|
template <typename T>
|
|
explicit JSON(T i, typename enable_if<is_integral<T>::value &&
|
|
!is_same<T, bool>::value>::type* = 0)
|
|
: Internal(static_cast<int>(i)), Type(Class::Integral) {}
|
|
|
|
template <typename T>
|
|
explicit JSON(T f, typename enable_if<is_floating_point<T>::value>::type* = 0)
|
|
: Internal(static_cast<double>(f)), Type(Class::Floating) {}
|
|
|
|
template <typename T>
|
|
explicit JSON(T s,
|
|
typename enable_if<is_convertible<T, string>::value>::type* = 0)
|
|
: Internal(string(s)), Type(Class::String) {}
|
|
|
|
explicit JSON(std::nullptr_t) : Internal(), Type(Class::Null) {}
|
|
|
|
static JSON Make(Class type) {
|
|
JSON ret;
|
|
ret.SetType(type);
|
|
return ret;
|
|
}
|
|
|
|
static JSON Load(const string&);
|
|
|
|
template <typename T>
|
|
void append(T arg) {
|
|
SetType(Class::Array);
|
|
Internal.List->emplace_back(arg);
|
|
}
|
|
|
|
template <typename T, typename... U>
|
|
void append(T arg, U... args) {
|
|
append(arg);
|
|
append(args...);
|
|
}
|
|
|
|
template <typename T>
|
|
typename enable_if<is_same<T, bool>::value, JSON&>::type operator=(T b) {
|
|
SetType(Class::Boolean);
|
|
Internal.Bool = b;
|
|
return *this;
|
|
}
|
|
|
|
template <typename T>
|
|
typename enable_if<is_integral<T>::value && !is_same<T, bool>::value,
|
|
JSON&>::type
|
|
operator=(T i) {
|
|
SetType(Class::Integral);
|
|
Internal.Int = i;
|
|
return *this;
|
|
}
|
|
|
|
template <typename T>
|
|
typename enable_if<is_floating_point<T>::value, JSON&>::type operator=(T f) {
|
|
SetType(Class::Floating);
|
|
Internal.Float = f;
|
|
return *this;
|
|
}
|
|
|
|
template <typename T>
|
|
typename enable_if<is_convertible<T, string>::value, JSON&>::type operator=(
|
|
T s) {
|
|
SetType(Class::String);
|
|
*Internal.String = string(s);
|
|
return *this;
|
|
}
|
|
|
|
JSON& operator[](const string& key) {
|
|
SetType(Class::Object);
|
|
return Internal.Map->operator[](key);
|
|
}
|
|
|
|
JSON& operator[](unsigned index) {
|
|
SetType(Class::Array);
|
|
if (index >= Internal.List->size()) Internal.List->resize(index + 1);
|
|
return Internal.List->operator[](index);
|
|
}
|
|
|
|
JSON& at(const string& key) { return operator[](key); }
|
|
|
|
const JSON& at(const string& key) const { return Internal.Map->at(key); }
|
|
|
|
JSON& at(unsigned index) { return operator[](index); }
|
|
|
|
const JSON& at(unsigned index) const { return Internal.List->at(index); }
|
|
|
|
int length() const {
|
|
if (Type == Class::Array)
|
|
return Internal.List->size();
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
bool hasKey(const string& key) const {
|
|
if (Type == Class::Object)
|
|
return Internal.Map->find(key) != Internal.Map->end();
|
|
return false;
|
|
}
|
|
|
|
int size() const {
|
|
if (Type == Class::Object)
|
|
return Internal.Map->size();
|
|
else if (Type == Class::Array)
|
|
return Internal.List->size();
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
Class JSONType() const { return Type; }
|
|
|
|
/// Functions for getting primitives from the JSON object.
|
|
bool IsNull() const { return Type == Class::Null; }
|
|
|
|
string ToString() const {
|
|
bool b;
|
|
return std::move(ToString(&b));
|
|
}
|
|
string ToString(bool* ok) const {
|
|
*ok = (Type == Class::String);
|
|
return *ok ? std::move(json_escape(*Internal.String)) : string("");
|
|
}
|
|
|
|
double ToFloat() const {
|
|
bool b;
|
|
return ToFloat(&b);
|
|
}
|
|
double ToFloat(bool* ok) const {
|
|
*ok = (Type == Class::Floating);
|
|
return *ok ? Internal.Float : 0.0;
|
|
}
|
|
|
|
int ToInt() const {
|
|
bool b;
|
|
return ToInt(&b);
|
|
}
|
|
int ToInt(bool* ok) const {
|
|
*ok = (Type == Class::Integral);
|
|
return *ok ? Internal.Int : 0;
|
|
}
|
|
|
|
bool ToBool() const {
|
|
bool b;
|
|
return ToBool(&b);
|
|
}
|
|
bool ToBool(bool* ok) const {
|
|
*ok = (Type == Class::Boolean);
|
|
return *ok ? Internal.Bool : false;
|
|
}
|
|
|
|
JSONWrapper<map<string, JSON>> ObjectRange() {
|
|
if (Type == Class::Object)
|
|
return JSONWrapper<map<string, JSON>>(Internal.Map);
|
|
return JSONWrapper<map<string, JSON>>(nullptr);
|
|
}
|
|
|
|
JSONWrapper<deque<JSON>> ArrayRange() {
|
|
if (Type == Class::Array) return JSONWrapper<deque<JSON>>(Internal.List);
|
|
return JSONWrapper<deque<JSON>>(nullptr);
|
|
}
|
|
|
|
JSONConstWrapper<map<string, JSON>> ObjectRange() const {
|
|
if (Type == Class::Object)
|
|
return JSONConstWrapper<map<string, JSON>>(Internal.Map);
|
|
return JSONConstWrapper<map<string, JSON>>(nullptr);
|
|
}
|
|
|
|
JSONConstWrapper<deque<JSON>> ArrayRange() const {
|
|
if (Type == Class::Array)
|
|
return JSONConstWrapper<deque<JSON>>(Internal.List);
|
|
return JSONConstWrapper<deque<JSON>>(nullptr);
|
|
}
|
|
|
|
string dump(int depth = 1, string tab = " ") const {
|
|
string pad = "";
|
|
for (int i = 0; i < depth; ++i, pad += tab) {
|
|
}
|
|
|
|
switch (Type) {
|
|
case Class::Null:
|
|
return "null";
|
|
case Class::Object: {
|
|
string s = "{\n";
|
|
bool skip = true;
|
|
for (auto& p : *Internal.Map) {
|
|
if (!skip) s += ",\n";
|
|
s += (pad + "\"" + p.first + "\" : " + p.second.dump(depth + 1, tab));
|
|
skip = false;
|
|
}
|
|
s += ("\n" + pad.erase(0, 2) + "}");
|
|
return s;
|
|
}
|
|
case Class::Array: {
|
|
string s = "[";
|
|
bool skip = true;
|
|
for (auto& p : *Internal.List) {
|
|
if (!skip) s += ", ";
|
|
s += p.dump(depth + 1, tab);
|
|
skip = false;
|
|
}
|
|
s += "]";
|
|
return s;
|
|
}
|
|
case Class::String:
|
|
return "\"" + json_escape(*Internal.String) + "\"";
|
|
case Class::Floating:
|
|
return std::to_string(Internal.Float);
|
|
case Class::Integral:
|
|
return std::to_string(Internal.Int);
|
|
case Class::Boolean:
|
|
return Internal.Bool ? "true" : "false";
|
|
default:
|
|
return "";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
friend std::ostream& operator<<(std::ostream&, const JSON&);
|
|
|
|
private:
|
|
void SetType(Class type) {
|
|
if (type == Type) return;
|
|
|
|
ClearInternal();
|
|
|
|
switch (type) {
|
|
case Class::Null:
|
|
Internal.Map = nullptr;
|
|
break;
|
|
case Class::Object:
|
|
Internal.Map = new map<string, JSON>();
|
|
break;
|
|
case Class::Array:
|
|
Internal.List = new deque<JSON>();
|
|
break;
|
|
case Class::String:
|
|
Internal.String = new string();
|
|
break;
|
|
case Class::Floating:
|
|
Internal.Float = 0.0;
|
|
break;
|
|
case Class::Integral:
|
|
Internal.Int = 0;
|
|
break;
|
|
case Class::Boolean:
|
|
Internal.Bool = false;
|
|
break;
|
|
}
|
|
|
|
Type = type;
|
|
}
|
|
|
|
private:
|
|
/* beware: only call if YOU know that Internal is allocated. No checks
|
|
performed here. This function should be called in a constructed JSON just
|
|
before you are going to overwrite Internal...
|
|
*/
|
|
void ClearInternal() {
|
|
switch (Type) {
|
|
case Class::Object:
|
|
delete Internal.Map;
|
|
break;
|
|
case Class::Array:
|
|
delete Internal.List;
|
|
break;
|
|
case Class::String:
|
|
delete Internal.String;
|
|
break;
|
|
default: {
|
|
};
|
|
}
|
|
}
|
|
|
|
private:
|
|
Class Type = Class::Null;
|
|
};
|
|
|
|
JSON Array() { return std::move(JSON::Make(JSON::Class::Array)); }
|
|
|
|
template <typename... T>
|
|
JSON Array(T... args) {
|
|
JSON arr = JSON::Make(JSON::Class::Array);
|
|
arr.append(args...);
|
|
return std::move(arr);
|
|
}
|
|
|
|
JSON Object() { return std::move(JSON::Make(JSON::Class::Object)); }
|
|
|
|
std::ostream& operator<<(std::ostream& os, const JSON& json) {
|
|
os << json.dump();
|
|
return os;
|
|
}
|
|
|
|
namespace { // NOLINT
|
|
JSON parse_next(const string&, size_t&);
|
|
|
|
void consume_ws(const string& str, size_t& offset) { // NOLINT
|
|
while (isspace(str[offset])) ++offset;
|
|
}
|
|
|
|
JSON parse_object(const string& str, size_t& offset) { // NOLINT
|
|
JSON Object = JSON::Make(JSON::Class::Object);
|
|
|
|
++offset;
|
|
consume_ws(str, offset);
|
|
if (str[offset] == '}') {
|
|
++offset;
|
|
return std::move(Object);
|
|
}
|
|
|
|
while (true) {
|
|
JSON Key = parse_next(str, offset);
|
|
consume_ws(str, offset);
|
|
if (str[offset] != ':') {
|
|
std::cerr << "Error: Object: Expected colon, found '" << str[offset]
|
|
<< "'\n";
|
|
break;
|
|
}
|
|
consume_ws(str, ++offset);
|
|
JSON Value = parse_next(str, offset);
|
|
Object[Key.ToString()] = Value;
|
|
|
|
consume_ws(str, offset);
|
|
if (str[offset] == ',') {
|
|
++offset;
|
|
continue;
|
|
} else if (str[offset] == '}') {
|
|
++offset;
|
|
break;
|
|
} else {
|
|
std::cerr << "ERROR: Object: Expected comma, found '" << str[offset]
|
|
<< "'\n";
|
|
break;
|
|
}
|
|
}
|
|
|
|
return std::move(Object);
|
|
}
|
|
|
|
JSON parse_array(const string& str, size_t& offset) { // NOLINT
|
|
JSON Array = JSON::Make(JSON::Class::Array);
|
|
unsigned index = 0;
|
|
|
|
++offset;
|
|
consume_ws(str, offset);
|
|
if (str[offset] == ']') {
|
|
++offset;
|
|
return std::move(Array);
|
|
}
|
|
|
|
while (true) {
|
|
Array[index++] = parse_next(str, offset);
|
|
consume_ws(str, offset);
|
|
|
|
if (str[offset] == ',') {
|
|
++offset;
|
|
continue;
|
|
} else if (str[offset] == ']') {
|
|
++offset;
|
|
break;
|
|
} else {
|
|
std::cerr << "ERROR: Array: Expected ',' or ']', found '" << str[offset]
|
|
<< "'\n";
|
|
return std::move(JSON::Make(JSON::Class::Array));
|
|
}
|
|
}
|
|
|
|
return std::move(Array);
|
|
}
|
|
|
|
JSON parse_string(const string& str, size_t& offset) { // NOLINT
|
|
JSON String;
|
|
string val;
|
|
for (char c = str[++offset]; c != '\"'; c = str[++offset]) {
|
|
if (c == '\\') {
|
|
switch (str[++offset]) {
|
|
case '\"':
|
|
val += '\"';
|
|
break;
|
|
case '\\':
|
|
val += '\\';
|
|
break;
|
|
case '/':
|
|
val += '/';
|
|
break;
|
|
case 'b':
|
|
val += '\b';
|
|
break;
|
|
case 'f':
|
|
val += '\f';
|
|
break;
|
|
case 'n':
|
|
val += '\n';
|
|
break;
|
|
case 'r':
|
|
val += '\r';
|
|
break;
|
|
case 't':
|
|
val += '\t';
|
|
break;
|
|
case 'u': {
|
|
val += "\\u";
|
|
for (unsigned i = 1; i <= 4; ++i) {
|
|
c = str[offset + i];
|
|
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
|
|
(c >= 'A' && c <= 'F')) {
|
|
val += c;
|
|
} else {
|
|
std::cerr << "ERROR: String: Expected hex character in unicode "
|
|
"escape, found '"
|
|
<< c << "'\n";
|
|
return std::move(JSON::Make(JSON::Class::String));
|
|
}
|
|
}
|
|
offset += 4;
|
|
} break;
|
|
default:
|
|
val += '\\';
|
|
break;
|
|
}
|
|
} else {
|
|
val += c;
|
|
}
|
|
}
|
|
++offset;
|
|
String = val;
|
|
return std::move(String);
|
|
}
|
|
|
|
JSON parse_number(const string& str, size_t& offset) { // NOLINT
|
|
JSON Number;
|
|
string val, exp_str;
|
|
char c;
|
|
bool isDouble = false;
|
|
int exp = 0;
|
|
while (true) {
|
|
c = str[offset++];
|
|
if ((c == '-') || (c >= '0' && c <= '9')) {
|
|
val += c;
|
|
} else if (c == '.') {
|
|
val += c;
|
|
isDouble = true;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (c == 'E' || c == 'e') {
|
|
c = str[offset++];
|
|
if (c == '-') {
|
|
++offset;
|
|
exp_str += '-';
|
|
}
|
|
while (true) {
|
|
c = str[offset++];
|
|
if (c >= '0' && c <= '9') {
|
|
exp_str += c;
|
|
} else if (!isspace(c) && c != ',' && c != ']' && c != '}') {
|
|
std::cerr << "ERROR: Number: Expected a number for exponent, found '"
|
|
<< c << "'\n";
|
|
return std::move(JSON::Make(JSON::Class::Null));
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
exp = std::stol(exp_str);
|
|
} else if (!isspace(c) && c != ',' && c != ']' && c != '}') {
|
|
std::cerr << "ERROR: Number: unexpected character '" << c << "'\n";
|
|
return std::move(JSON::Make(JSON::Class::Null));
|
|
}
|
|
--offset;
|
|
|
|
if (isDouble) {
|
|
Number = std::stod(val) * std::pow(10, exp);
|
|
} else {
|
|
if (!exp_str.empty())
|
|
Number = std::stol(val) * std::pow(10, exp);
|
|
else
|
|
Number = std::stol(val);
|
|
}
|
|
return std::move(Number);
|
|
}
|
|
|
|
JSON parse_bool(const string& str, size_t& offset) { // NOLINT
|
|
JSON Bool;
|
|
if (str.substr(offset, 4) == "true") {
|
|
Bool = true;
|
|
} else if (str.substr(offset, 5) == "false") {
|
|
Bool = false;
|
|
} else {
|
|
std::cerr << "ERROR: Bool: Expected 'true' or 'false', found '"
|
|
<< str.substr(offset, 5) << "'\n";
|
|
return std::move(JSON::Make(JSON::Class::Null));
|
|
}
|
|
offset += (Bool.ToBool() ? 4 : 5);
|
|
return std::move(Bool);
|
|
}
|
|
|
|
JSON parse_null(const string& str, size_t& offset) { // NOLINT
|
|
JSON Null;
|
|
if (str.substr(offset, 4) != "null") {
|
|
std::cerr << "ERROR: Null: Expected 'null', found '"
|
|
<< str.substr(offset, 4) << "'\n";
|
|
return std::move(JSON::Make(JSON::Class::Null));
|
|
}
|
|
offset += 4;
|
|
return std::move(Null);
|
|
}
|
|
|
|
JSON parse_next(const string& str, size_t& offset) { // NOLINT
|
|
char value;
|
|
consume_ws(str, offset);
|
|
value = str[offset];
|
|
switch (value) {
|
|
case '[':
|
|
return std::move(parse_array(str, offset));
|
|
case '{':
|
|
return std::move(parse_object(str, offset));
|
|
case '\"':
|
|
return std::move(parse_string(str, offset));
|
|
case 't':
|
|
case 'f':
|
|
return std::move(parse_bool(str, offset));
|
|
case 'n':
|
|
return std::move(parse_null(str, offset));
|
|
default:
|
|
if ((value <= '9' && value >= '0') || value == '-')
|
|
return std::move(parse_number(str, offset));
|
|
}
|
|
std::cerr << "ERROR: Parse: Unknown starting character '" << value << "'\n";
|
|
return JSON();
|
|
}
|
|
} // namespace
|
|
|
|
JSON JSON::Load(const string& str) {
|
|
size_t offset = 0;
|
|
return std::move(parse_next(str, offset));
|
|
}
|
|
|
|
} // namespace json
|
|
|
|
#endif // UTILS_JSON_H_
|