Repository: ClickHouse/ClickHouse
File: src/Functions/FunctionsJSON.cpp (C++)

Code before:
            if (sub_object_type)
                sub_object_subcolumn = data_type_object.getSubcolumn(sub_object_name, object_column);

            /// If neither subcolumn exists, the path is absent thus return defaults.
            if (!literal_subcolumn && !sub_object_subcolumn)
                insertManyDefaults();

            /// Merge literal and subobject subcolumns (same logic as `getObjectElement' in tupleElement.cpp):
            /// For each row: prefer literal (scalar) value, if absent, use subobject (nested JSON).
            ColumnPtr merged;
            DataTypePtr merged_type;

            if (!sub_object_subcolumn
                || sub_object_subcolumn->getNumberOfDefaultRows() == sub_object_subcolumn->size())
            {
                /// No nested subobject at this path just use the literal subcolumn.
                merged = literal_subcolumn;
                merged_type = literal_type;
            }
            else if (!literal_subcolumn
                     || literal_subcolumn->getNumberOfDefaultRows() == literal_subcolumn->size())
            {
                /// No literal value at this path just use the subobject subcolumn.
                merged = sub_object_subcolumn;
                merged_type = sub_object_type;
            }
            else
            {
                /// Both exist: merge row-by-row.
                auto casted_sub_object = castColumn({sub_object_subcolumn, sub_object_type, ""}, literal_type);
                auto result_col = literal_type->createColumn();
                for (size_t i = 0; i < input_rows_count; ++i)
                {
                    if (!literal_subcolumn->isDefaultAt(i))
                        result_col->insertFrom(*literal_subcolumn, i);
                    else if (!sub_object_subcolumn->isDefaultAt(i))
                        result_col->insertFrom(*casted_sub_object, i);
                    else
                        result_col->insertDefault();
                }
                merged = std::move(result_col);
                merged_type = literal_type;
            }

            /// For JSONExtractRaw: serialize each value as a JSON string
            /// using ISerialization::serializeTextJSON.
            constexpr bool is_extract_raw = std::string_view(TName::name) == std::string_view("JSONExtractRaw")
                        || std::string_view(TName::name) == std::string_view("JSONExtractRawCaseInsensitive");

            if constexpr (is_extract_raw)
            {

Review comment (refactor):
It's not correct. `sub_object_subcolumn` will contain empty JSON object on rows where there is no sub-object (because the type of this subcolumn is `JSON`). So this will lead to empty object to be casted to the result type instead of default value:
```sql
:) select JSONExtract(arrayJoin(['{"a" : {"b" : 42}}', '{}'])::JSON, 'a', 'String')

   ┌─JSONExtract(⋯, 'String')─┐
1. │ {"b":42}                 │
2. │ {}                       │
   └──────────────────────────┘

:) select JSONExtract(arrayJoin(['{"a" : {"b" : 42}}', '{}']), 'a', 'String')

   ┌─JSONExtract(⋯, 'String')─┐
1. │ {"b":42}                 │
2. │                          │
   └──────────────────────────┘
```

So we should go to the branch with row-by-row merge here.
Please, add this case to the tests

Code after:
            {
                /// Extract subobject subcolumn (json.^`path` returns nested object at that path).
                String sub_object_name = "^`" + path + "`";
                auto sub_object_type = data_type_object.getSubcolumnType(sub_object_name);
                ColumnPtr sub_object_subcolumn = data_type_object.getSubcolumn(sub_object_name, object_column);

                /// Merge literal and subobject subcolumns (same logic as `getObjectElement' in tupleElement.cpp):
                /// For each row: prefer literal (scalar) value, if absent, use subobject (nested JSON).
                if (sub_object_subcolumn->getNumberOfDefaultRows() == sub_object_subcolumn->size())
                {
                    /// No nested subobject at this path (only literal exists).
                    merged = literal_subcolumn;
                    merged_type = literal_type;
                }
                else
                {
                    /// Both exist: merge row-by-row.
                    auto casted_sub_object = castColumn({sub_object_subcolumn, sub_object_type, ""}, literal_type);
                    auto result_col = literal_type->createColumn();
                    for (size_t i = 0; i < input_rows_count; ++i)
                    {
                        if (!literal_subcolumn->isDefaultAt(i))
                            result_col->insertFrom(*literal_subcolumn, i);
                        else if (!sub_object_subcolumn->isDefaultAt(i))
                            result_col->insertFrom(*casted_sub_object, i);
                        else
                            result_col->insertDefault();
                    }
                    merged = std::move(result_col);
                    merged_type = literal_type;
                }
            }

            /// For JSONExtractRaw: serialize each value as a JSON string
            constexpr bool is_extract_raw = std::string_view(TName::name) == std::string_view("JSONExtractRaw")
                        || std::string_view(TName::name) == std::string_view("JSONExtractRawCaseInsensitive");

            if constexpr (is_extract_raw)
            {
                auto raw_col = ColumnString::create();
                auto serialization = merged_type->getDefaultSerialization();
                for (size_t i = 0; i < input_rows_count; ++i)
                {
                    if (merged->isDefaultAt(i))
                    {
                        raw_col->insertDefault();
                    }
                    else
                    {
                        WriteBufferFromOwnString buf;
                        serialization->serializeTextJSON(*merged, i, buf, format_settings);