--- parser3/src/classes/json.C 2024/09/10 19:09:56 1.59 +++ parser3/src/classes/json.C 2025/12/17 20:34:20 1.69 @@ -1,7 +1,7 @@ /** @file Parser: @b json parser class. - Copyright (c) 2000-2023 Art. Lebedev Studio (http://www.artlebedev.com) + Copyright (c) 2000-2024 Art. Lebedev Studio (http://www.artlebedev.com) Authors: Konstantin Morshnev */ @@ -10,7 +10,7 @@ #include "pa_request.h" #include "pa_vbool.h" -//#include "pa_varray.h" +#include "pa_varray.h" #include "pa_charset.h" #include "pa_charsets.h" @@ -20,19 +20,24 @@ #include "pa_vxdoc.h" #endif -volatile const char * IDENT_JSON_C="$Id: json.C,v 1.59 2024/09/10 19:09:56 moko Exp $"; +volatile const char * IDENT_JSON_C="$Id: json.C,v 1.69 2025/12/17 20:34:20 moko Exp $"; // class class MJson: public Methoded { public: MJson(); + + override Value* get_element(const String&); + override const VJunction* put_element(const String&, Value*); }; // global variable DECLARE_CLASS_VAR(json, new MJson); +bool handle_array_default=true; + // methods struct Json : public PA_Allocated { Stack stack; @@ -55,7 +60,7 @@ struct Json : public PA_Allocated { Json(Charset* acharset): stack(), key_stack(), key(NULL), result(NULL), hook_object(NULL), hook_array(NULL), request(NULL), charset(acharset), taint(String::L_TAINTED), handle_double(true), handle_int(true), - handle_array(true), distinct(D_EXCEPTION){} + handle_array(handle_array_default), distinct(D_EXCEPTION){} bool set_distinct(const String &value){ if (value == "first") distinct = D_FIRST; @@ -73,6 +78,31 @@ struct Json : public PA_Allocated { } }; + +Value* MJson::get_element(const String& aname) { + if (aname=="array"){ + return new VString(*new String(handle_array_default ? "array" : "hash")); + } + return Methoded::get_element(aname); +} + +const VJunction* MJson::put_element(const String& aname, Value* avalue) { + if (aname=="array"){ + Json json(NULL); + if (avalue->get_string()){ + const String& sarray=avalue->as_string(); + if (json.set_handle_array(sarray)){ + handle_array_default=json.handle_array; + return 0; + } + throw Exception(PARSER_RUNTIME, &sarray, "$json:array must be 'array' or 'hash'"); + } + throw Exception(PARSER_RUNTIME, 0, "$json:array must be 'array' or 'hash'"); + } + return Methoded::put_element(aname, avalue); +} + + static void set_json_value(Json *json, Value *value){ VHashBase *top = json->stack.top_value(); if(json->key == NULL){ @@ -93,7 +123,7 @@ static void set_json_value(Json *json, V if (top->hash().put_dont_replace(*json->key, value)){ for(int i=2;;i++){ String key; - key << *json->key << "_" << format(i, 0); + key << *json->key << "_" << pa_uitoa(i); if (!top->hash().put_dont_replace(key, value)) break; } } @@ -103,11 +133,15 @@ static void set_json_value(Json *json, V } } -String* json_string(Json *json, const char *value, uint32_t length){ - String::C result = json->charset !=NULL ? - Charset::transcode(String::C(value, length), pa_UTF8_charset, *json->charset) : - String::C(pa_strdup(value, length), length); - return new String(result, json->taint); +String* json_string(Json *json, const char *value){ + /* do_callback_withbuf guarantees a null-terminated value */ + if (size_t length = strlen(value)){ + String::C result = json->charset !=NULL ? + Charset::transcode(String::C(value, length), pa_UTF8_charset, *json->charset) : + String::C(pa_strdup(value, length), length); + return new String(result, json->taint); + } + return (String*)&String::Empty; } static Value *json_hook(Request &r, Junction *hook, String* key, Value* value){ @@ -119,7 +153,7 @@ static Value *json_hook(Request &r, Junc }); } -static int json_callback(Json *json, int type, const char *value, uint32_t length) +static int json_callback(Json *json, int type, const char *value, uint32_t) { switch(type) { case JSON_OBJECT_BEGIN:{ @@ -153,8 +187,7 @@ static int json_callback(Json *json, int json->key=NULL; json->stack.push(new VHash); } else { - VHashBase *v = new VHash; -// VHashBase *v = json->handle_array ? (VHashBase *)new VArray : (VHashBase *)new VHash; + VHashBase *v = json->handle_array ? (VHashBase *)new VArray : (VHashBase *)new VHash; if (json->stack.count()) set_json_value(json, v); json->stack.push(v); } @@ -175,23 +208,23 @@ static int json_callback(Json *json, int } break; case JSON_KEY: - json->key = json_string(json, value, length); + json->key = json_string(json, value); break; case JSON_INT: if (json->handle_int){ - set_json_value(json, new VDouble( json_string(json, value, length)->as_double() )); + set_json_value(json, new VDouble( json_string(json, value)->as_double() )); } else { // JSON_STRING - set_json_value(json, new VString(*json_string(json, value, length))); + set_json_value(json, new VString(*json_string(json, value))); } break; case JSON_FLOAT: if (json->handle_double){ - set_json_value(json, new VDouble( json_string(json, value, length)->as_double() )); + set_json_value(json, new VDouble( json_string(json, value)->as_double() )); break; } // else is JSON_STRING case JSON_STRING: - set_json_value(json, new VString(*json_string(json, value, length))); + set_json_value(json, new VString(*json_string(json, value))); break; case JSON_NULL: set_json_value(json, VVoid::get()); @@ -379,10 +412,10 @@ static void _parse(Request& r, MethodPar if (json.result) r.write(*json.result); } -const uint ANTI_ENDLESS_JSON_STRING_RECOURSION=128; +const uint ANTI_ENDLESS_JSON_STRING_RECURSION=128; char *get_indent(uint level){ - static char* cache[ANTI_ENDLESS_JSON_STRING_RECOURSION]={}; + static char* cache[ANTI_ENDLESS_JSON_STRING_RECURSION]={}; if (!cache[level]){ char *result = static_cast(pa_malloc_atomic(level+1)); memset(result, '\t', level); @@ -393,7 +426,7 @@ char *get_indent(uint level){ } String *get_delim(uint level){ - static String* cache[ANTI_ENDLESS_JSON_STRING_RECOURSION]={}; + static String* cache[ANTI_ENDLESS_JSON_STRING_RECURSION]={}; if (!cache[level]){ char *result = static_cast(pa_malloc_atomic(level+2+1+1)); @@ -408,7 +441,7 @@ String *get_delim(uint level){ } String *get_array_delim(uint level){ - static String* cache[ANTI_ENDLESS_JSON_STRING_RECOURSION]={}; + static String* cache[ANTI_ENDLESS_JSON_STRING_RECURSION]={}; if (!cache[level]){ char *result = static_cast(pa_malloc_atomic(level+2+1)); @@ -421,16 +454,16 @@ String *get_array_delim(uint level){ return cache[level]; } -class Json_string_recoursion { +class Json_string_recursion { Json_options& foptions; public: - Json_string_recoursion(Json_options& aoptions) : foptions(aoptions) { - if(++foptions.json_string_recoursion==ANTI_ENDLESS_JSON_STRING_RECOURSION) + Json_string_recursion(Json_options& aoptions) : foptions(aoptions) { + if(++foptions.json_string_recursion==ANTI_ENDLESS_JSON_STRING_RECURSION) throw Exception(PARSER_RUNTIME, 0, "call canceled - endless json recursion detected"); } - ~Json_string_recoursion() { - if(foptions.json_string_recoursion) - foptions.json_string_recoursion--; + ~Json_string_recursion() { + if(foptions.json_string_recursion) + foptions.json_string_recursion--; } }; @@ -440,24 +473,24 @@ const String* Json_options::hash_json_st if(!hash || !hash->count()) return new String("{}", String::L_AS_IS); - Json_string_recoursion go_down(*this); + Json_string_recursion go_down(*this); String& result = *new String("{\n", String::L_AS_IS); if (indent){ String *delim=NULL; - indent=get_indent(json_string_recoursion); + indent=get_indent(json_string_recursion); for(HashStringValue::Iterator i(*hash); i; i.next() ){ if (delim){ result << *delim; } else { result << indent << "\""; - delim = get_delim(json_string_recoursion); + delim = get_delim(json_string_recursion); } result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this); } - result << "\n" << (indent=get_indent(json_string_recoursion-1)) << "}"; + result << "\n" << (indent=get_indent(json_string_recursion-1)) << "}"; } else { @@ -478,31 +511,31 @@ const String* Json_options::array_json_s if(!array || !array->count()) return new String("[]", String::L_AS_IS); - Json_string_recoursion go_down(*this); + Json_string_recursion go_down(*this); String& result = *new String("[\n", String::L_AS_IS); if (indent){ String *delim=NULL; - indent=get_indent(json_string_recoursion); + indent=get_indent(json_string_recursion); for(ArrayValue::Iterator i(*array); i; i.next() ){ if (delim){ result << *delim; } else { result << indent; - delim = get_array_delim(json_string_recoursion); + delim = get_array_delim(json_string_recursion); } - result << value_json_string(i.key(), i.value() ? *i.value() : *VVoid::get(), *this); + result << value_json_string(String::Body::uitoa(i.index()), i.value() ? *i.value() : static_cast(*VVoid::get()), *this); } - result << "\n" << (indent=get_indent(json_string_recoursion-1)) << "]"; + result << "\n" << (indent=get_indent(json_string_recursion-1)) << "]"; } else { bool need_delim=false; for(ArrayValue::Iterator i(*array); i; i.next() ){ if(need_delim) result << ",\n"; - result << value_json_string(i.key(), i.value() ? *i.value() : *VVoid::get(), *this); + result << value_json_string(String::Body::uitoa(i.index()), i.value() ? *i.value() : static_cast(*VVoid::get()), *this); need_delim=true; } result << "\n]"; @@ -516,26 +549,26 @@ const String* Json_options::array_compac if(!array || !array->count()) return new String("[]", String::L_AS_IS); - Json_string_recoursion go_down(*this); + Json_string_recursion go_down(*this); String& result = *new String("[\n", String::L_AS_IS); if (indent){ String *delim=NULL; - indent=get_indent(json_string_recoursion); + indent=get_indent(json_string_recursion); for(ArrayValue::Iterator i(*array); i; i.next() ){ if (i.value()){ if (delim){ result << *delim; } else { result << indent; - delim = get_array_delim(json_string_recoursion); + delim = get_array_delim(json_string_recursion); } - result << value_json_string(i.key(), *i.value(), *this); + result << value_json_string(String::Body::uitoa(i.index()), *i.value(), *this); } } - result << "\n" << (indent=get_indent(json_string_recoursion-1)) << "]"; + result << "\n" << (indent=get_indent(json_string_recursion-1)) << "]"; } else { @@ -543,7 +576,7 @@ const String* Json_options::array_compac for(ArrayValue::Iterator i(*array); i; i.next() ){ if (i.value()){ if(need_delim) result << ",\n"; - result << value_json_string(i.key(), *i.value(), *this); + result << value_json_string(String::Body::uitoa(i.index()), *i.value(), *this); need_delim=true; } } @@ -566,9 +599,10 @@ const String& value_json_string(String:: options.methods->put(v.type(), method ? method : VVoid::get()); } if(method && !method->is_void()) { + static const String::Body sindent("indent"); Junction* junction=method->get_junction(); HashStringValue* params_hash=options.params && options.indent ? options.params->get_hash() : NULL; - Temp_hash_value indent(params_hash, "indent", new VString(*new String(options.indent, String::L_AS_IS))); + Temp_hash_value indent(params_hash, sindent, new VString(*new String(options.indent, String::L_AS_IS))); Value *params[]={new VString(*new String(key, String::L_JSON)), &v, options.params ? options.params : VVoid::get()}; @@ -591,67 +625,54 @@ static void _string(Request& r, MethodPa if(HashStringValue* options=params.as_hash(1)) { json.params=¶ms[1]; HashStringValue* methods=new HashStringValue(); - int valid_options=0; HashStringValue* vvalue; for(HashStringValue::Iterator i(*options); i; i.next() ){ String::Body key=i.key(); Value* value=i.value(); if(key == "skip-unknown"){ json.skip_unknown=r.process(*value).as_bool(); - valid_options++; } else if(key == "one-line"){ json.one_line=r.process(*value).as_bool(); - valid_options++; } else if(key == "date" && value->is_string()){ const String& svalue=value->as_string(); if(!json.set_date_format(svalue)) throw Exception(PARSER_RUNTIME, &svalue, "must be 'sql-string', 'gmt-string', 'iso-string' or 'unix-timestamp'"); - valid_options++; } else if(key == "indent"){ if(value->is_string()){ json.indent=value->as_string().cstr(); - json.json_string_recoursion=strlen(json.indent); + json.json_string_recursion=strlen(json.indent); } else json.indent=r.process(*value).as_bool() ? "" : NULL; - valid_options++; } else if(key == "table" && value->is_string()){ const String& svalue=value->as_string(); if(!json.set_table_format(svalue)) throw Exception(PARSER_RUNTIME, &svalue, "must be 'array', 'object' or 'compact'"); - valid_options++; } else if(key == "array" && value->is_string()){ const String& svalue=value->as_string(); if(!json.set_array_format(svalue)) throw Exception(PARSER_RUNTIME, &svalue, "must be 'array', 'object' or 'compact'"); - valid_options++; } else if(key == "file" && value->is_string()){ const String& svalue=value->as_string(); if(!json.set_file_format(svalue)) throw Exception(PARSER_RUNTIME, &svalue, "must be 'base64', 'text' or 'stat'"); - valid_options++; } else if(key == "void" && value->is_string()){ const String& svalue=value->as_string(); if(!json.set_void_format(svalue)) throw Exception(PARSER_RUNTIME, &svalue, "must be 'string' or 'null'"); - valid_options++; #ifdef XML } else if(key == "xdoc" && (vvalue = value->get_hash())){ json.xdoc_options=new XDocOutputOptions(); json.xdoc_options->append(r, vvalue); - valid_options++; #endif } else if(Junction* junction=value->get_junction()){ if(!junction->method || !junction->method->params_names || junction->method->params_count != 3) throw Exception(PARSER_RUNTIME, 0, "$.%s must be parser method with 3 parameters", key.cstr()); methods->put(key, value); - valid_options++; - } + } else + throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); } - if(valid_options!=options->count()) - throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); - // special handling for $._default - if(VHashBase* vhash=static_cast(params[1].as(VHASH_TYPE))) + if(VHashBase* vhash=dynamic_cast(¶ms[1])) if(Value* value=vhash->get_default()) { if(!value->is_string()){ Junction* junction=value->get_junction(); @@ -672,7 +693,7 @@ static void _string(Request& r, MethodPa for(char *c=result;*c;c++) if(*c=='\n') *c=' '; - result_body=result; + result_body=String::Body(result); } r.write(*new String(result_body, String::L_AS_IS)); }