Index: src/wp-admin/customize.php
===================================================================
--- src/wp-admin/customize.php	(revision 30026)
+++ src/wp-admin/customize.php	(working copy)
@@ -274,7 +274,7 @@
 
 	?>
 	<script type="text/javascript">
-		var _wpCustomizeSettings = <?php echo json_encode( $settings ); ?>;
+		var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
 	</script>
 </div>
 </body>
Index: src/wp-admin/includes/ajax-actions.php
===================================================================
--- src/wp-admin/includes/ajax-actions.php	(revision 30026)
+++ src/wp-admin/includes/ajax-actions.php	(working copy)
@@ -286,7 +286,7 @@
 		);
 	}
 
-	wp_die( json_encode( $return ) );
+	wp_die( wp_json_encode( $return ) );
 }
 
 /**
@@ -1365,7 +1365,7 @@
 
 		$markup = ob_get_clean();
 
-		echo json_encode(array(
+		echo wp_json_encode(array(
 			'replace-id' => $type . '-' . $item->name,
 			'markup' => $markup,
 		));
@@ -1394,7 +1394,7 @@
 	if ( ! isset( $results ) )
 		wp_die( 0 );
 
-	echo json_encode( $results );
+	echo wp_json_encode( $results );
 	echo "\n";
 
 	wp_die();
@@ -1840,7 +1840,7 @@
 	if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ) ) ) {
 		$wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'], false );
 		if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
-			echo json_encode( array(
+			echo wp_json_encode( array(
 				'success' => false,
 				'data'    => array(
 					'message'  => __( 'The uploaded file is not a valid image. Please try again.' ),
@@ -1855,7 +1855,7 @@
 	$attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
 
 	if ( is_wp_error( $attachment_id ) ) {
-		echo json_encode( array(
+		echo wp_json_encode( array(
 			'success' => false,
 			'data'    => array(
 				'message'  => $attachment_id->get_error_message(),
@@ -1877,7 +1877,7 @@
 	if ( ! $attachment = wp_prepare_attachment_for_js( $attachment_id ) )
 		wp_die();
 
-	echo json_encode( array(
+	echo wp_json_encode( array(
 		'success' => true,
 		'data'    => $attachment,
 	) );
@@ -1902,7 +1902,7 @@
 	switch ( $_POST['do'] ) {
 		case 'save' :
 			$msg = wp_save_image($attachment_id);
-			$msg = json_encode($msg);
+			$msg = wp_json_encode($msg);
 			wp_die( $msg );
 			break;
 		case 'scale' :
Index: src/wp-admin/includes/class-wp-list-table.php
===================================================================
--- src/wp-admin/includes/class-wp-list-table.php	(revision 30026)
+++ src/wp-admin/includes/class-wp-list-table.php	(working copy)
@@ -1058,7 +1058,7 @@
 			$response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] );
 		}
 
-		die( json_encode( $response ) );
+		die( wp_json_encode( $response ) );
 	}
 
 	/**
@@ -1075,6 +1075,6 @@
 			)
 		);
 
-		printf( "<script type='text/javascript'>list_args = %s;</script>\n", json_encode( $args ) );
+		printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) );
 	}
 }
Index: src/wp-admin/includes/class-wp-themes-list-table.php
===================================================================
--- src/wp-admin/includes/class-wp-themes-list-table.php	(revision 30026)
+++ src/wp-admin/includes/class-wp-themes-list-table.php	(working copy)
@@ -273,7 +273,7 @@
 		if ( is_array( $extra_args ) )
 			$args = array_merge( $args, $extra_args );
 
-		printf( "<script type='text/javascript'>var theme_list_args = %s;</script>\n", json_encode( $args ) );
+		printf( "<script type='text/javascript'>var theme_list_args = %s;</script>\n", wp_json_encode( $args ) );
 		parent::_js_vars();
 	}
 }
Index: src/wp-admin/includes/media.php
===================================================================
--- src/wp-admin/includes/media.php	(revision 30026)
+++ src/wp-admin/includes/media.php	(working copy)
@@ -1826,7 +1826,7 @@
 	$large_size_w = 1024;
 ?>
 var resize_height = <?php echo $large_size_h; ?>, resize_width = <?php echo $large_size_w; ?>,
-wpUploaderInit = <?php echo json_encode($plupload_init); ?>;
+wpUploaderInit = <?php echo wp_json_encode( $plupload_init ); ?>;
 </script>
 
 <div id="plupload-upload-ui" class="hide-if-no-js">
Index: src/wp-admin/includes/misc.php
===================================================================
--- src/wp-admin/includes/misc.php	(revision 30026)
+++ src/wp-admin/includes/misc.php	(working copy)
@@ -621,7 +621,7 @@
 			<div class="color-option <?php echo ( $color == $current_color ) ? 'selected' : ''; ?>">
 				<input name="admin_color" id="admin_color_<?php echo esc_attr( $color ); ?>" type="radio" value="<?php echo esc_attr( $color ); ?>" class="tog" <?php checked( $color, $current_color ); ?> />
 				<input type="hidden" class="css_url" value="<?php echo esc_url( $color_info->url ); ?>" />
-				<input type="hidden" class="icon_colors" value="<?php echo esc_attr( json_encode( array( 'icons' => $color_info->icon_colors ) ) ); ?>" />
+				<input type="hidden" class="icon_colors" value="<?php echo esc_attr( wp_json_encode( array( 'icons' => $color_info->icon_colors ) ) ); ?>" />
 				<label for="admin_color_<?php echo esc_attr( $color ); ?>"><?php echo esc_html( $color_info->name ); ?></label>
 				<table class="color-palette">
 					<tr>
@@ -665,7 +665,7 @@
 		$icon_colors = array( 'base' => '#999', 'focus' => '#2ea2cc', 'current' => '#fff' );
 	}
 
-	echo '<script type="text/javascript">var _wpColorScheme = ' . json_encode( array( 'icons' => $icon_colors ) ) . ";</script>\n";
+	echo '<script type="text/javascript">var _wpColorScheme = ' . wp_json_encode( array( 'icons' => $icon_colors ) ) . ";</script>\n";
 }
 add_action( 'admin_head', 'wp_color_scheme_settings' );
 
Index: src/wp-admin/includes/nav-menu.php
===================================================================
--- src/wp-admin/includes/nav-menu.php	(revision 30026)
+++ src/wp-admin/includes/nav-menu.php	(working copy)
@@ -356,7 +356,7 @@
 				if ( 'markup' == $response_format ) {
 					echo walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', array( get_post( $object_id ) ) ), 0, (object) $args );
 				} elseif ( 'json' == $response_format ) {
-					echo json_encode(
+					echo wp_json_encode(
 						array(
 							'ID' => $object_id,
 							'post_title' => get_the_title( $object_id ),
@@ -373,7 +373,7 @@
 					echo walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', array( get_term( $object_id, $object_type ) ) ), 0, (object) $args );
 				} elseif ( 'json' == $response_format ) {
 					$post_obj = get_term( $object_id, $object_type );
-					echo json_encode(
+					echo wp_json_encode(
 						array(
 							'ID' => $object_id,
 							'post_title' => $post_obj->name,
@@ -401,7 +401,7 @@
 					$var_by_ref = get_the_ID();
 					echo walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', array( get_post( $var_by_ref ) ) ), 0, (object) $args );
 				} elseif ( 'json' == $response_format ) {
-					echo json_encode(
+					echo wp_json_encode(
 						array(
 							'ID' => get_the_ID(),
 							'post_title' => get_the_title(),
@@ -422,7 +422,7 @@
 				if ( 'markup' == $response_format ) {
 					echo walk_nav_menu_tree( array_map('wp_setup_nav_menu_item', array( $term ) ), 0, (object) $args );
 				} elseif ( 'json' == $response_format ) {
-					echo json_encode(
+					echo wp_json_encode(
 						array(
 							'ID' => $term->term_id,
 							'post_title' => $term->name,
Index: src/wp-admin/includes/template.php
===================================================================
--- src/wp-admin/includes/template.php	(revision 30026)
+++ src/wp-admin/includes/template.php	(working copy)
@@ -1952,7 +1952,7 @@
 		<script type="text/javascript">
 		//<![CDATA[
 		(function($){
-			var options = <?php echo json_encode( $args ); ?>, setup;
+			var options = <?php echo wp_json_encode( $args ); ?>, setup;
 
 			if ( ! options )
 				return;
Index: src/wp-includes/class-wp-customize-manager.php
===================================================================
--- src/wp-includes/class-wp-customize-manager.php	(revision 30026)
+++ src/wp-includes/class-wp-customize-manager.php	(working copy)
@@ -517,7 +517,7 @@
 
 		?>
 		<script type="text/javascript">
-			var _wpCustomizeSettings = <?php echo json_encode( $settings ); ?>;
+			var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
 		</script>
 		<?php
 	}
Index: src/wp-includes/class-wp-customize-widgets.php
===================================================================
--- src/wp-includes/class-wp-customize-widgets.php	(revision 30026)
+++ src/wp-includes/class-wp-customize-widgets.php	(working copy)
@@ -740,7 +740,7 @@
 		$wp_scripts->add_data(
 			'customize-widgets',
 			'data',
-			sprintf( 'var _wpCustomizeWidgetsSettings = %s;', json_encode( $settings ) )
+			sprintf( 'var _wpCustomizeWidgetsSettings = %s;', wp_json_encode( $settings ) )
 		);
 	}
 
@@ -1055,7 +1055,7 @@
 
 		?>
 		<script type="text/javascript">
-			var _wpWidgetCustomizerPreviewSettings = <?php echo json_encode( $settings ); ?>;
+			var _wpWidgetCustomizerPreviewSettings = <?php echo wp_json_encode( $settings ); ?>;
 		</script>
 		<?php
 	}
Index: src/wp-includes/class-wp-editor.php
===================================================================
--- src/wp-includes/class-wp-editor.php	(revision 30026)
+++ src/wp-includes/class-wp-editor.php	(working copy)
@@ -500,7 +500,7 @@
 				);
 
 				if ( ! empty( $mce_external_plugins ) ) {
-					self::$first_init['external_plugins'] = json_encode( $mce_external_plugins );
+					self::$first_init['external_plugins'] = wp_json_encode( $mce_external_plugins );
 				}
 
 				$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
@@ -986,7 +986,7 @@
 			$mce_translation['_dir'] = 'rtl';
 		}
 
-		return "tinymce.addI18n( '$mce_locale', " . json_encode( $mce_translation ) . ");\n" .
+		return "tinymce.addI18n( '$mce_locale', " . wp_json_encode( $mce_translation ) . ");\n" .
 			"tinymce.ScriptLoader.markDone( '$baseurl/langs/$mce_locale.js' );\n";
 	}
 
Index: src/wp-includes/class.wp-scripts.php
===================================================================
--- src/wp-includes/class.wp-scripts.php	(revision 30026)
+++ src/wp-includes/class.wp-scripts.php	(working copy)
@@ -167,7 +167,7 @@
 			$l10n[$key] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8');
 		}
 
-		$script = "var $object_name = " . json_encode($l10n) . ';';
+		$script = "var $object_name = " . wp_json_encode( $l10n ) . ';';
 
 		if ( !empty($after) )
 			$script .= "\n$after;";
Index: src/wp-includes/functions.php
===================================================================
--- src/wp-includes/functions.php	(revision 30026)
+++ src/wp-includes/functions.php	(working copy)
@@ -2613,6 +2613,119 @@
 }
 
 /**
+ * Encode a variable into JSON, with some sanity checks
+ *
+ * @since 4.1.0
+ *
+ * @param mixed $data    Variable (usually an array or object) to encode as JSON
+ * @param int   $options Options to be passed to json_encode(). Default 0.
+ * @param int   $depth   Maximum depth to walk through $data. Must be greater than 0, default 512.t
+ *
+ * @return bool|string The JSON encoded string, or false if it cannot be encoded
+ */
+function wp_json_encode( $data, $options = 0, $depth = 512 ) {
+	// json_encode has had extra params added over the years.
+	// $options was added in 5.3, and $depth in 5.5.
+	// We need to make sure we call it with the correct arguments.
+	if ( version_compare( PHP_VERSION, '5.5', '>=' ) ) {
+		$args = array( $data, $options, $depth );
+	} else if ( version_compare( PHP_VERSION, '5.3', '>=' ) ) {
+		$args = array( $data, $options );
+	} else {
+		$args = array( $data );
+	}
+
+	$json = call_user_func_array( 'json_encode', $args );
+
+	if ( false !== $json ) {
+		// If json_encode was successful, no need to do more sanity checking
+		return $json;
+	}
+
+	try {
+		$args[0] = _wp_json_sanity_check( $data, $depth );
+	} catch ( Exception $e ) {
+		return false;
+	}
+
+	return call_user_func_array( 'json_encode', $args );
+}
+
+/**
+ * @ignore
+ */
+function _wp_json_sanity_check( $data, $depth ) {
+	if ( $depth < 0 ) {
+		throw new Exception( 'Reached depth limit' );
+	}
+
+	if ( is_array( $data ) ) {
+		$output = array();
+		foreach ( $data as $id => $el ) {
+			// Don't forget to sanitize the ID!
+			if ( is_string( $id ) ) {
+				$clean_id = _wp_json_convert_string( $id );
+			} else {
+				$clean_id = $id;
+			}
+
+			// Check the element type, so that we're only recursing if we really have to
+			if ( is_array( $el ) || is_object( $el ) ) {
+				$output[ $clean_id ] = _wp_json_sanity_check( $el, $depth - 1 );
+			} else if ( is_string( $el ) ) {
+				$output[ $clean_id ] = _wp_json_convert_string( $el );
+			} else {
+				$output[ $clean_id ] = $el;
+			}
+		}
+	} else if ( is_object( $data ) ) {
+		$output = new stdClass;
+		foreach ( $data as $id => $el ) {
+			if ( is_string( $id ) ) {
+				$clean_id = _wp_json_convert_string( $id );
+			} else {
+				$clean_id = $id;
+			}
+
+			if ( is_array( $el ) || is_object( $el ) ) {
+				$output->$clean_id = _wp_json_sanity_check( $el, $depth - 1 );
+			} else if ( is_string( $el ) ) {
+				$output->$clean_id = _wp_json_convert_string( $el );
+			} else {
+				$output->$clean_id = $el;
+			}
+		}
+	} else if ( is_string( $data ) ) {
+		return _wp_json_convert_string( $data );
+	} else {
+		return $data;
+	}
+
+	return $output;
+}
+
+/**
+ * @ignore
+ */
+function _wp_json_convert_string( $string ) {
+	static $use_mb = null;
+	if ( is_null( $use_mb ) ) {
+		$use_mb = function_exists( 'mb_convert_encoding' );
+	}
+
+	if ( $use_mb ) {
+		$encoding = mb_detect_encoding( $string, mb_detect_order(), true );
+		if ( $encoding ) {
+			return mb_convert_encoding( $string, 'UTF-8', $encoding );
+		} else {
+			return mb_convert_encoding( $string, 'UTF-8', 'UTF-8' );
+		}
+	} else {
+		return wp_check_invalid_utf8( $data, true );
+	}
+}
+
+/**
  * Send a JSON response back to an Ajax request.
  *
  * @since 3.5.0
@@ -2622,7 +2735,7 @@
  */
 function wp_send_json( $response ) {
 	@header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
-	echo json_encode( $response );
+	echo wp_json_encode( $response );
 	if ( defined( 'DOING_AJAX' ) && DOING_AJAX )
 		wp_die();
 	else
Index: src/wp-includes/media.php
===================================================================
--- src/wp-includes/media.php	(revision 30026)
+++ src/wp-includes/media.php	(working copy)
@@ -1398,7 +1398,7 @@
 	}
 	?></ol>
 	</noscript>
-	<script type="application/json" class="wp-playlist-script"><?php echo json_encode( $data ) ?></script>
+	<script type="application/json" class="wp-playlist-script"><?php echo wp_json_encode( $data ) ?></script>
 </div>
 	<?php
 	return ob_get_clean();
@@ -2582,7 +2582,7 @@
 		'limitExceeded' => is_multisite() && ! is_upload_space_available()
 	);
 
-	$script = 'var _wpPluploadSettings = ' . json_encode( $settings ) . ';';
+	$script = 'var _wpPluploadSettings = ' . wp_json_encode( $settings ) . ';';
 
 	if ( $data )
 		$script = "$data\n$script";
Index: src/wp-includes/theme.php
===================================================================
--- src/wp-includes/theme.php	(revision 30026)
+++ src/wp-includes/theme.php	(working copy)
@@ -1935,7 +1935,7 @@
 		),
 	);
 
-	$script = 'var _wpCustomizeLoaderSettings = ' . json_encode( $settings ) . ';';
+	$script = 'var _wpCustomizeLoaderSettings = ' . wp_json_encode( $settings ) . ';';
 
 	$data = $wp_scripts->get_data( 'customize-loader', 'data' );
 	if ( $data )
Index: src/wp-includes/update.php
===================================================================
--- src/wp-includes/update.php	(revision 30026)
+++ src/wp-includes/update.php	(working copy)
@@ -94,7 +94,7 @@
 	);
 
 	$post_body = array(
-		'translations' => json_encode( $translations ),
+		'translations' => wp_json_encode( $translations ),
 	);
 
 	if ( is_array( $extra_stats ) )
@@ -274,16 +274,16 @@
 	$options = array(
 		'timeout' => $timeout,
 		'body' => array(
-			'plugins'      => json_encode( $to_send ),
-			'translations' => json_encode( $translations ),
-			'locale'       => json_encode( $locales ),
-			'all'          => json_encode( true ),
+			'plugins'      => wp_json_encode( $to_send ),
+			'translations' => wp_json_encode( $translations ),
+			'locale'       => wp_json_encode( $locales ),
+			'all'          => wp_json_encode( true ),
 		),
 		'user-agent' => 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' )
 	);
 
 	if ( $extra_stats ) {
-		$options['body']['update_stats'] = json_encode( $extra_stats );
+		$options['body']['update_stats'] = wp_json_encode( $extra_stats );
 	}
 
 	$url = $http_url = 'http://api.wordpress.org/plugins/update-check/1.1/';
@@ -437,15 +437,15 @@
 	$options = array(
 		'timeout' => $timeout,
 		'body' => array(
-			'themes'       => json_encode( $request ),
-			'translations' => json_encode( $translations ),
-			'locale'       => json_encode( $locales ),
+			'themes'       => wp_json_encode( $request ),
+			'translations' => wp_json_encode( $translations ),
+			'locale'       => wp_json_encode( $locales ),
 		),
 		'user-agent'	=> 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' )
 	);
 
 	if ( $extra_stats ) {
-		$options['body']['update_stats'] = json_encode( $extra_stats );
+		$options['body']['update_stats'] = wp_json_encode( $extra_stats );
 	}
 
 	$url = $http_url = 'http://api.wordpress.org/themes/update-check/1.1/';
Index: tests/phpunit/tests/functions.php
===================================================================
--- tests/phpunit/tests/functions.php	(revision 30026)
+++ tests/phpunit/tests/functions.php	(working copy)
@@ -529,4 +529,46 @@
 		$this->assertCount( 8, $urls );
 		$this->assertEquals( array_slice( $original_urls, 0, 8 ), $urls );
 	}
+
+	/**
+	 * @ticket 28786
+	 */
+	function test_wp_json_encode() {
+		$this->assertEquals( wp_json_encode( 'a' ), '"a"' );
+		$this->assertEquals( wp_json_encode( '这' ), '"\u8fd9"' );
+
+		$old_charsets = $charsets = mb_detect_order();
+		if ( ! in_array( 'EUC-JP', $charsets ) ) {
+			$charsets[] = 'EUC-JP';
+			mb_detect_order( $charsets );
+		}
+
+		$eucjp = mb_convert_encoding( 'aあb', 'EUC-JP', 'UTF-8' );
+		$utf8 = mb_convert_encoding( $eucjp, 'UTF-8', 'EUC-JP' );
+
+		$this->assertEquals( 'aあb', $utf8 );
+
+		$this->assertEquals( wp_json_encode( $eucjp ), '"a\u3042b"' );
+
+		$this->assertEquals( wp_json_encode( array( 'a' ) ), '["a"]' );
+
+		$object = new stdClass;
+		$object->a = 'b';
+		$this->assertEquals( wp_json_encode( $object ), '{"a":"b"}' );
+
+		mb_detect_order( $old_charsets );
+	}
+
+	/**
+	 * @ticket 28786
+	 */
+	function test_wp_json_encode_depth() {
+		$data = array( array( array( 1, 2, 3 ) ) );
+		$json = wp_json_encode( $data, 0, 1 );
+		$this->assertFalse( $json );
+
+		$data = array( 'あ', array( array( 1, 2, 3 ) ) );
+		$json = wp_json_encode( $data, 0, 1 );
+		$this->assertFalse( $json );
+	}
 }
