diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py
index 434bef65f..6c2b94f3c 100644
--- a/yt_dlp/YoutubeDL.py
+++ b/yt_dlp/YoutubeDL.py
@@ -65,6 +65,7 @@
     ExistingVideoReached,
     expand_path,
     ExtractorError,
+    filter_dict,
     float_or_none,
     format_bytes,
     format_field,
@@ -1574,13 +1575,9 @@ def process_ie_result(self, ie_result, download=True, extra_info=None):
             if not info:
                 return info
 
-            force_properties = dict(
-                (k, v) for k, v in ie_result.items() if v is not None)
-            for f in ('_type', 'url', 'id', 'extractor', 'extractor_key', 'ie_key'):
-                if f in force_properties:
-                    del force_properties[f]
             new_result = info.copy()
-            new_result.update(force_properties)
+            new_result.update(filter_dict(ie_result, lambda k, v: (
+                v is not None and k not in {'_type', 'url', 'id', 'extractor', 'extractor_key', 'ie_key'})))
 
             # Extracted info may not be a video result (i.e.
             # info.get('_type', 'video') != video) but rather an url or
diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py
index d3d13c40c..d0e57da23 100644
--- a/yt_dlp/extractor/common.py
+++ b/yt_dlp/extractor/common.py
@@ -49,6 +49,7 @@
     error_to_compat_str,
     extract_attributes,
     ExtractorError,
+    filter_dict,
     fix_xml_ampersands,
     float_or_none,
     format_field,
@@ -1588,7 +1589,7 @@ def traverse_json_ld(json_ld, at_top_level=True):
                     break
         traverse_json_ld(json_ld)
 
-        return dict((k, v) for k, v in info.items() if v is not None)
+        return filter_dict(info)
 
     def _search_nextjs_data(self, webpage, video_id, *, transform_source=None, fatal=True, **kw):
         return self._parse_json(
diff --git a/yt_dlp/extractor/rai.py b/yt_dlp/extractor/rai.py
index 34f127285..9d243b2be 100644
--- a/yt_dlp/extractor/rai.py
+++ b/yt_dlp/extractor/rai.py
@@ -11,6 +11,7 @@
 from ..utils import (
     determine_ext,
     ExtractorError,
+    filter_dict,
     find_xpath_attr,
     fix_xml_ampersands,
     GeoRestrictedError,
@@ -110,11 +111,11 @@ def _extract_relinker_info(self, relinker_url, video_id, audio_only=False):
         if not audio_only:
             formats.extend(self._create_http_urls(relinker_url, formats))
 
-        return dict((k, v) for k, v in {
+        return filter_dict({
             'is_live': is_live,
             'duration': duration,
             'formats': formats,
-        }.items() if v is not None)
+        })
 
     def _create_http_urls(self, relinker_url, fmts):
         _RELINKER_REG = r'https?://(?P<host>[^/]+?)/(?:i/)?(?P<extra>[^/]+?)/(?P<path>.+?)/(?P<id>\d+)(?:_(?P<quality>[\d\,]+))?(?:\.mp4|/playlist\.m3u8).+?'
diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py
index 72f11691f..08e30d18f 100644
--- a/yt_dlp/utils.py
+++ b/yt_dlp/utils.py
@@ -3105,16 +3105,16 @@ def try_get(src, getter, expected_type=None):
                 return v
 
 
+def filter_dict(dct, cndn=lambda _, v: v is not None):
+    return {k: v for k, v in dct.items() if cndn(k, v)}
+
+
 def merge_dicts(*dicts):
     merged = {}
     for a_dict in dicts:
         for k, v in a_dict.items():
-            if v is None:
-                continue
-            if (k not in merged
-                    or (isinstance(v, compat_str) and v
-                        and isinstance(merged[k], compat_str)
-                        and not merged[k])):
+            if (v is not None and k not in merged
+                    or isinstance(v, str) and merged[k] == ''):
                 merged[k] = v
     return merged