--- parser3/src/classes/json.C 2021/01/15 16:28:36 1.56 +++ parser3/src/classes/json.C 2024/10/16 03:28:30 1.64 @@ -1,7 +1,8 @@ /** @file Parser: @b json parser class. - Copyright (c) 2000-2020 Art. Lebedev Studio (http://www.artlebedev.com) + Copyright (c) 2000-2023 Art. Lebedev Studio (http://www.artlebedev.com) + Authors: Konstantin Morshnev */ #include "classes.h" @@ -9,6 +10,7 @@ #include "pa_request.h" #include "pa_vbool.h" +#include "pa_varray.h" #include "pa_charset.h" #include "pa_charsets.h" @@ -18,22 +20,27 @@ #include "pa_vxdoc.h" #endif -volatile const char * IDENT_JSON_C="$Id: json.C,v 1.56 2021/01/15 16:28:36 moko Exp $"; +volatile const char * IDENT_JSON_C="$Id: json.C,v 1.64 2024/10/16 03:28:30 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; + Stack stack; Stack key_stack; String* key; @@ -48,11 +55,12 @@ struct Json : public PA_Allocated { bool handle_double; bool handle_int; + bool handle_array; enum Distinct { D_EXCEPTION, D_FIRST, D_LAST, D_ALL } distinct; 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), - distinct(D_EXCEPTION){} + handle_array(handle_array_default), distinct(D_EXCEPTION){} bool set_distinct(const String &value){ if (value == "first") distinct = D_FIRST; @@ -61,12 +69,44 @@ struct Json : public PA_Allocated { else return false; return true; } + + bool set_handle_array(const String &value){ + if (value == "array") handle_array = true; + else if (value == "hash") handle_array = false; + else return false; + return true; + } }; + +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){ - VHash *top = json->stack.top_value(); + VHashBase *top = json->stack.top_value(); if(json->key == NULL){ - top->hash().put(format(top->get_hash()->count(), 0), value); + top->add(value); } else { switch (json->distinct){ case Json::D_EXCEPTION: @@ -83,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; } } @@ -138,14 +178,15 @@ static int json_callback(Json *json, int break; } case JSON_ARRAY_BEGIN:{ - VHash *v = new VHash(); if (json->hook_array){ json->key_stack.push(json->key); json->key=NULL; + json->stack.push(new VHash); } else { + VHashBase *v = json->handle_array ? (VHashBase *)new VArray : (VHashBase *)new VHash; if (json->stack.count()) set_json_value(json, v); + json->stack.push(v); } - json->stack.push(v); break; } case JSON_ARRAY_END: @@ -325,10 +366,16 @@ static void _parse(Request& r, MethodPar valid_options++; } if(Value* value=options->get("array")) { - json.hook_array=value->get_junction(); - json.request=&r; - if (!json.hook_array || !json.hook_array->method || !json.hook_array->method->params_names || !(json.hook_array->method->params_count == 2)) - throw Exception(PARSER_RUNTIME, 0, "$.array must be parser method with 2 parameters"); + if(value->get_string()){ + const String& sarray=value->as_string(); + if (!json.set_handle_array(sarray)) + throw Exception(PARSER_RUNTIME, &sarray, "$.array must be parser method with 2 parameters or 'array' or 'hash'"); + } else { + json.hook_array=value->get_junction(); + json.request=&r; + if (!json.hook_array || !json.hook_array->method || !json.hook_array->method->params_names || !(json.hook_array->method->params_count == 2)) + throw Exception(PARSER_RUNTIME, 0, "$.array must be parser method with 2 parameters or 'array' or 'hash'"); + } valid_options++; } if(valid_options!=options->count()) @@ -361,10 +408,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); @@ -375,7 +422,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)); @@ -389,16 +436,30 @@ String *get_delim(uint level){ return cache[level]; } -class Json_string_recoursion { +String *get_array_delim(uint level){ + static String* cache[ANTI_ENDLESS_JSON_STRING_RECURSION]={}; + + if (!cache[level]){ + char *result = static_cast(pa_malloc_atomic(level+2+1)); + result[0]=','; + result[1]='\n'; + memset(result+2, '\t', level); + result[level+2]='\0'; + return cache[level] = new String(result, String::L_AS_IS); + } + return cache[level]; +} + +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--; } }; @@ -408,24 +469,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 { @@ -442,6 +503,86 @@ const String* Json_options::hash_json_st return &result; } +const String* Json_options::array_json_string(ArrayValue *array) { + if(!array || !array->count()) + return new String("[]", String::L_AS_IS); + + 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_recursion); + for(ArrayValue::Iterator i(*array); i; i.next() ){ + if (delim){ + result << *delim; + } else { + result << indent; + delim = get_array_delim(json_string_recursion); + } + result << value_json_string(i.key(), i.value() ? *i.value() : *VVoid::get(), *this); + } + 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); + need_delim=true; + } + result << "\n]"; + + } + + return &result; +} + +const String* Json_options::array_compact_json_string(ArrayValue *array) { + if(!array || !array->count()) + return new String("[]", String::L_AS_IS); + + 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_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_recursion); + } + result << value_json_string(i.key(), *i.value(), *this); + } + } + result << "\n" << (indent=get_indent(json_string_recursion-1)) << "]"; + + } else { + + bool need_delim=false; + 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); + need_delim=true; + } + } + result << "\n]"; + + } + + return &result; +} + static bool based_on(HashStringValue::key_type key, HashStringValue::value_type /*value*/, Value* v) { return v->is(key.cstr()); } @@ -498,7 +639,7 @@ static void _string(Request& r, MethodPa } 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()){ @@ -506,6 +647,11 @@ static void _string(Request& r, MethodPa 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)) @@ -534,7 +680,7 @@ static void _string(Request& r, MethodPa 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(); @@ -565,5 +711,5 @@ static void _string(Request& r, MethodPa MJson::MJson(): Methoded("json") { add_native_method("parse", Method::CT_STATIC, _parse, 1, 2); - add_native_method("string", Method::CT_ANY, _string, 1, 2); + add_native_method("string", Method::CT_STATIC, _string, 1, 2); }