Variant HHVM_FUNCTION(json_encode, const Variant& value, int64_t options /* = 0 */, int64_t depth /* = 512 */) { // Special case for resource since VariableSerializer does not take care of it if (value.isResource()) { json_set_last_error_code(json_error_codes::JSON_ERROR_UNSUPPORTED_TYPE); if (options & k_JSON_PARTIAL_OUTPUT_ON_ERROR) { return "null"; } return false; } json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); VariableSerializer vs(VariableSerializer::Type::JSON, options); vs.setDepthLimit(depth); String json = vs.serializeValue(value, !(options & k_JSON_FB_UNLIMITED)); if ((json_get_last_error_code() != json_error_codes::JSON_ERROR_NONE) && !(options & k_JSON_PARTIAL_OUTPUT_ON_ERROR)) { return false; } return json; }
Variant f_json_decode(const String& json, bool assoc /* = false */, CVarRef options /* = 0 */) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); if (json.empty()) { return uninit_null(); } int64_t json_options = options.toInt64(); if (options.isBoolean() && options.toBooleanVal()) { json_options = k_JSON_FB_LOOSE; } const int64_t supported_options = k_JSON_FB_LOOSE | k_JSON_FB_COLLECTIONS | k_JSON_FB_STABLE_MAPS; Variant z; if (JSON_parser(z, json.data(), json.size(), assoc, (json_options & supported_options))) { return z; } if (json.size() == 4) { if (!strcasecmp(json.data(), "null")) return uninit_null(); if (!strcasecmp(json.data(), "true")) return true; } else if (json.size() == 5 && !strcasecmp(json.data(), "false")) { return false; } int64_t p; double d; DataType type = json->isNumericWithVal(p, d, 0); if (type == KindOfInt64) { return p; } else if (type == KindOfDouble) { return d; } char ch0 = json.charAt(0); if (json.size() > 1 && ch0 == '"' && json.charAt(json.size() - 1) == '"') { return json.substr(1, json.size() - 2); } if ((json_options & k_JSON_FB_LOOSE) && json.size() > 1 && ch0 == '\'' && json.charAt(json.size() - 1) == '\'') { return json.substr(1, json.size() - 2); } if (ch0 == '{' || ch0 == '[') { /* invalid JSON string */ json_set_last_error_code(json_error_codes::JSON_ERROR_SYNTAX); } assert(json_get_last_error_code() != json_error_codes::JSON_ERROR_NONE); return uninit_null(); }
// Handles output of `json_encode` with fallback value for // partial output on errors, and `false` otherwise. Variant json_guard_error_result(const String& partial_error_output, int64_t options /* = 0*/) { int is_partial_output = options & k_JSON_PARTIAL_OUTPUT_ON_ERROR; // Issue a warning on unsupported type in case of HH syntax. if (json_get_last_error_code() == json_error_codes::JSON_ERROR_UNSUPPORTED_TYPE && RuntimeOption::EnableHipHopSyntax) { // Unhandled case is always returned as `false`; for partial output // we render "null" value. raise_warning("json_encode(): type is unsupported, encoded as %s", is_partial_output ? "null" : "false"); } if (is_partial_output) { return partial_error_output; } return false; }
TypedValue HHVM_FUNCTION(json_encode, const Variant& value, int64_t options, int64_t depth) { // Special case for resource since VariableSerializer does not take care of it if (value.isResource()) { json_set_last_error_code(json_error_codes::JSON_ERROR_UNSUPPORTED_TYPE); return tvReturn(json_guard_error_result("null", options)); } json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); VariableSerializer vs(VariableSerializer::Type::JSON, options); vs.setDepthLimit(depth); String json = vs.serializeValue(value, !(options & k_JSON_FB_UNLIMITED)); assertx(json.get() != nullptr); if (UNLIKELY(StructuredLog::coinflip(RuntimeOption::EvalSerDesSampleRate))) { StructuredLog::logSerDes("json", "ser", json, value); } if (json_get_last_error_code() != json_error_codes::JSON_ERROR_NONE) { return tvReturn(json_guard_error_result(json, options)); } return tvReturn(std::move(json)); }
Variant f_json_decode(const String& json, bool assoc /* = false */, CVarRef options /* = 0 */) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); if (json.empty()) { return uninit_null(); } int64_t json_options = options.toInt64(); if (options.isBoolean() && options.toBooleanVal()) { json_options = k_JSON_FB_LOOSE; } const int64_t supported_options = k_JSON_FB_LOOSE | k_JSON_FB_COLLECTIONS | k_JSON_FB_STABLE_MAPS; int64_t parser_options = json_options & supported_options; Variant z; if (JSON_parser(z, json.data(), json.size(), assoc, parser_options)) { return z; } if (json.size() == 4) { if (!strcasecmp(json.data(), "null")) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return uninit_null(); } if (!strcasecmp(json.data(), "true")) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return true; } } else if (json.size() == 5 && !strcasecmp(json.data(), "false")) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return false; } int64_t p; double d; DataType type = json->isNumericWithVal(p, d, 0); if (type == KindOfInt64) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return p; } else if (type == KindOfDouble) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return d; } char ch0 = json.charAt(0); if (json.size() > 1 && ch0 == '"' && json.charAt(json.size() - 1) == '"') { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); // Wrap the string in an array to allow the JSON_parser to handle // things like unicode escape sequences, then unwrap to get result String wrapped("["); wrapped += json + "]"; // Stick to a normal hhvm array for the wrapper const int64_t mask = ~(k_JSON_FB_COLLECTIONS | k_JSON_FB_STABLE_MAPS); if (JSON_parser(z, wrapped.data(), wrapped.size(), false, parser_options & mask) && z.isArray()) { Array arr = z.toArray(); if ((arr.size() == 1) && arr.exists(0)) { return arr[0]; } // The input string could be something like: "foo","bar" // Which will parse inside the [] wrapper, but be invalid json_set_last_error_code(json_error_codes::JSON_ERROR_SYNTAX); } } if ((json_options & k_JSON_FB_LOOSE) && json.size() > 1 && ch0 == '\'' && json.charAt(json.size() - 1) == '\'') { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return json.substr(1, json.size() - 2); } if (ch0 == '{' || ch0 == '[') { /* invalid JSON string */ json_set_last_error_code(json_error_codes::JSON_ERROR_SYNTAX); } assert(json_get_last_error_code() != json_error_codes::JSON_ERROR_NONE); return uninit_null(); }
int f_json_last_error() { return (int) json_get_last_error_code(); }
int64_t HHVM_FUNCTION(json_last_error) { return (int) json_get_last_error_code(); }
TypedValue HHVM_FUNCTION(json_decode, const String& json, bool assoc, int64_t depth, int64_t options) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); if (json.empty()) { return make_tv<KindOfNull>(); } const int64_t supported_options = k_JSON_FB_LOOSE | k_JSON_FB_COLLECTIONS | k_JSON_FB_STABLE_MAPS | k_JSON_BIGINT_AS_STRING | k_JSON_FB_HACK_ARRAYS; int64_t parser_options = options & supported_options; Variant z; const auto ok = JSON_parser(z, json.data(), json.size(), assoc, depth, parser_options); if (UNLIKELY(StructuredLog::coinflip(RuntimeOption::EvalSerDesSampleRate))) { StructuredLog::logSerDes("json", "des", json, z); } if (ok) { return tvReturn(std::move(z)); } String trimmed = HHVM_FN(trim)(json, "\t\n\r "); if (trimmed.size() == 4) { if (!strcasecmp(trimmed.data(), "null")) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return make_tv<KindOfNull>(); } if (!strcasecmp(trimmed.data(), "true")) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return make_tv<KindOfBoolean>(true); } } else if (trimmed.size() == 5 && !strcasecmp(trimmed.data(), "false")) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return make_tv<KindOfBoolean>(false); } int64_t p; double d; DataType type = json.get()->isNumericWithVal(p, d, 0); if (type == KindOfInt64) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return make_tv<KindOfInt64>(p); } else if (type == KindOfDouble) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); if ((options & k_JSON_BIGINT_AS_STRING) && (json.toInt64() == LLONG_MAX || json.toInt64() == LLONG_MIN) && errno == ERANGE) { // Overflow bool is_float = false; for (int i = (trimmed[0] == '-' ? 1 : 0); i < trimmed.size(); ++i) { if (trimmed[i] < '0' || trimmed[i] > '9') { is_float = true; break; } } if (!is_float) { return tvReturn(trimmed); } } return make_tv<KindOfDouble>(d); } char ch0 = json.charAt(0); if (json.size() > 1 && ch0 == '"' && json.charAt(json.size() - 1) == '"') { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); // Wrap the string in an array to allow the JSON_parser to handle // things like unicode escape sequences, then unwrap to get result String wrapped("["); wrapped += json + "]"; // Stick to a normal hhvm array for the wrapper const int64_t mask = ~(k_JSON_FB_COLLECTIONS | k_JSON_FB_STABLE_MAPS); if (JSON_parser(z, wrapped.data(), wrapped.size(), false, depth, parser_options & mask) && z.isArray()) { Array arr = z.toArray(); if ((arr.size() == 1) && arr.exists(0)) { return tvReturn(arr[0]); } // The input string could be something like: "foo","bar" // Which will parse inside the [] wrapper, but be invalid json_set_last_error_code(json_error_codes::JSON_ERROR_SYNTAX); } } if ((options & k_JSON_FB_LOOSE) && json.size() > 1 && ch0 == '\'' && json.charAt(json.size() - 1) == '\'') { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return tvReturn(json.substr(1, json.size() - 2)); } assert(json_get_last_error_code() != json_error_codes::JSON_ERROR_NONE); return make_tv<KindOfNull>(); }
Variant HHVM_FUNCTION(json_decode, const String& json, bool assoc /* = false */, int64_t depth /* = 512 */, int64_t options /* = 0 */) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); if (json.empty()) { return init_null(); } const int64_t supported_options = k_JSON_FB_LOOSE | k_JSON_FB_COLLECTIONS | k_JSON_FB_STABLE_MAPS | k_JSON_BIGINT_AS_STRING; int64_t parser_options = options & supported_options; Variant z; if (JSON_parser(z, json.data(), json.size(), assoc, depth, parser_options)) { return z; } String trimmed = f_trim(json, "\t\n\r "); if (trimmed.size() == 4) { if (!strcasecmp(trimmed.data(), "null")) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return init_null(); } if (!strcasecmp(trimmed.data(), "true")) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return true; } } else if (trimmed.size() == 5 && !strcasecmp(trimmed.data(), "false")) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return false; } int64_t p; double d; DataType type = json.get()->isNumericWithVal(p, d, 0); if (type == KindOfInt64) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return p; } else if (type == KindOfDouble) { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); if ((options & k_JSON_BIGINT_AS_STRING) && (json.toInt64() == LLONG_MAX || json.toInt64() == LLONG_MIN) && errno == ERANGE) { // Overflow bool is_float = false; for (int i = (trimmed[0] == '-' ? 1 : 0); i < trimmed.size(); ++i) { if (trimmed[i] < '0' || trimmed[i] > '9') { is_float = true; break; } } if (!is_float) { return trimmed; } } return d; } char ch0 = json.charAt(0); if (json.size() > 1 && ch0 == '"' && json.charAt(json.size() - 1) == '"') { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); // Wrap the string in an array to allow the JSON_parser to handle // things like unicode escape sequences, then unwrap to get result String wrapped("["); wrapped += json + "]"; // Stick to a normal hhvm array for the wrapper const int64_t mask = ~(k_JSON_FB_COLLECTIONS | k_JSON_FB_STABLE_MAPS); if (JSON_parser(z, wrapped.data(), wrapped.size(), false, depth, parser_options & mask) && z.isArray()) { Array arr = z.toArray(); if ((arr.size() == 1) && arr.exists(0)) { return arr[0]; } // The input string could be something like: "foo","bar" // Which will parse inside the [] wrapper, but be invalid json_set_last_error_code(json_error_codes::JSON_ERROR_SYNTAX); } } if ((options & k_JSON_FB_LOOSE) && json.size() > 1 && ch0 == '\'' && json.charAt(json.size() - 1) == '\'') { json_set_last_error_code(json_error_codes::JSON_ERROR_NONE); return json.substr(1, json.size() - 2); } assert(json_get_last_error_code() != json_error_codes::JSON_ERROR_NONE); return init_null(); }