wikimedia/json-codec

用于将PHP对象序列化和反序列化为JSON的接口

v3.0.2 2024-07-22 18:29 UTC

This package is auto-updated.

Last update: 2024-09-20 03:14:14 UTC


README

Latest Stable Version License

JsonCodec

用于将PHP对象序列化和反序列化为JSON的接口。

有关此库的更多文档可以在mediawiki.org上找到。

使用方法

要使对象可序列化/反序列化为JSON,最简单的方法是使用JsonCodecableTrait并在你的类中实现两个方法,toJsonArray()和静态方法newFromJsonArray()

use Wikimedia\JsonCodec\JsonCodecable;

class SampleObject implements JsonCodecable {
	use JsonCodecableTrait;

	/** @var string */
	public string $property;

	// ....

	// Implement JsonCodecable using the JsonCodecableTrait

	/** @inheritDoc */
	public function toJsonArray(): array {
		return [
			'property' => $this->property,
		];
	}

	/** @inheritDoc */
	public static function newFromJsonArray( array $json ): SampleObject {
		return new SampleObject( $json['property'] );
	}
}

该示例的一个稍微复杂版本可以在tests/SampleObject.php中找到。

如果你的类需要显式管理 - 例如,需要使用工厂服务创建对象实例,你可以直接实现JsonCodecable

use Wikimedia\JsonCodec\JsonCodecable;

class ManagedObject implements JsonCodecable {
	public static function jsonClassCodec( ContainerInterface $serviceContainer ) {
		$factory = $serviceContainer->get( 'MyObjectFactory' );
		return new class( $factory ) implements JsonClassCodec {
			// ...
			public function toJsonArray( $obj ): array {
				// ...
			}
			public function newFromJsonArray( string $className, array $json ): ManagedObject {
				return $this->factory->create( $json[....] );
			}
		};
	}
}

一个完整的示例可以在tests/ManagedObject.php中找到。

请注意,由toJsonArray()返回的数组可以包含其他JsonCodecable对象,这些对象将被递归序列化。当在反序列化期间调用newFromJsonArray时,所有这些递归包含的对象都已反序列化回对象。

要序列化对象到JSON,请使用JsonCodec

use Wikimedia\JsonCodec\JsonCodec;

$services = ... your global services object, or null ...;
$codec = new JsonCodec( $services );

$string_result = $codec->toJsonString( $someComplexValue );
$someComplexValue = $codec->newFromJsonString( $string_result );

在某些情况下,您可能希望将此输出嵌入到另一个上下文中,或者使用非默认的json_encode选项进行格式化输出。在这些情况下,在编码/解码之前访问返回或接受编码数组的方法的访问可能很有用

$array_result = $codec->toJsonArray( $someComplexValue );
var_export($array_result); // pretty-print
$request->jsonResponse( [ 'error': false, 'embedded': $array_result ] );

$someComplexValue = $codec->fromJsonArray( $data['embedded'] );

处理“不可编码”的对象

在某些情况下,您可能能够序列化/反序列化不实现JsonCodecable的第三方对象。这可以通过使用JsonCodec方法::addCodecFor()来完成,该方法允许JsonCodec实例的创建者指定用于任意类名的JsonClassCodec。例如

use Wikimedia\JsonCodec\JsonCodec;

$codec = new JsonCodec( ...optional services object... );
$codec->addCodecFor( \DocumentFragment::class, new MyDOMSerializer() );

$string_result = $codec->toJsonString( $someComplexValue );

默认情况下,这是为了提供一个stdClass对象的序列化器。

如果逐个添加类编解码器不足以满足需求,例如如果您希望支持实现某些替代序列化接口的所有对象,您可以通过覆盖受保护的JsonCodec::codecFor()方法并返回适当的编解码器来继承JsonCodec。您的代码应如下所示

class MyCustomJsonCodec extends JsonCodec {
   protected function codecFor( string $className ): ?JsonClassCodec {
      $codec = parent::codecFor( $className );
      if ($codec === null && is_a($className, MyOwnSerializationType::class, true)) {
         $codec = new MyCustomSerializer();
         // Cache this for future use
         $this->addCodecFor( $className, $codec );
      }
      return $codec;
  }
}

一个完整的示例可以在tests/AlternateCodec.php中找到。

更简洁的输出

默认情况下,JsonCodec将适当对象类型的类名嵌入到JSON输出中,以启用可靠的反序列化。然而,在某些应用程序中,需要更简洁的JSON输出。通过为顶层调用::toJsonArray()newFromJsonArray()提供可选的“类提示”并在您的类编解码器中实现::jsonClassHintFor()方法,您可以在提供的提示与将要添加的信息匹配时抑制JSON中的不必要类型信息。例如

class SampleContainerObject implements JsonCodecable {
	use JsonCodecableTrait;

	/** @var mixed */
	public $contents;
	/** @var list<Foo> */
	public array $foos;

	// ...

	// Implement JsonCodecable using the JsonCodecableTrait

	/** @inheritDoc */
	public function toJsonArray(): array {
		return [ 'contents' => $this->contents, 'foos' => $this->foos ];
	}

	/** @inheritDoc */
	public static function newFromJsonArray( array $json ): SampleContainerObject {
		return new SampleContainerObject( $json['contents'], $json['foos'] );
	}

	/** @inheritDoc */
	public static function jsonClassHintFor( string $keyName ) {
		if ( $keyName === 'contents' ) {
			// Hint that the contained value is a SampleObject. It might be!
			return SampleObject::class;
		} elseif ( $keyName === 'foos' ) {
			// A hint with a modifier
			return Hint::build( Foo::class, Hint::LIST );
		}
		return null;
	}
}

您可以通过在序列化和反序列化时提供适当的提示来生成简洁的输出

use Wikimedia\JsonCodec\JsonCodec;

$codec = new JsonCodec();

$value = new SampleContainerObject( new SampleObject( 'sample' ), ... );
$string_result = $codec->toJsonString( $value, SampleContainerObject::class );

// $string_result is now:
//    {"contents":{"property":"sample"},"foos":[...]}'
// with no explicit type information.

// But we need to provide the same class hint when deserializing:
$value = $codec->newFromJsonString( $string_result, SampleContainerObject::class );

请注意,提供的值是一个提示。如果我们把除了SampleObject以外的值放入SampleContainerObject,该值的类型将被嵌入到JSON输出中,但不会破坏序列化/反序列化。

foos属性所示,要指示同质的列表或给定类型的数组,可以将Hint::build(...., Hint::LIST)作为类提示传递。具有给定类型属性值的stdClass对象可以使用Hint::build(...., Hint::STDCLASS)进行提示。

完整示例请参阅tests/SampleContainerObject.php

Hint::USE_SQUARE修饰符允许::toJsonArray()返回一个列表(请参阅array_is_list),并将该列表编码为JSON数组,使用方括号[]

Hint::ALLOW_OBJECT修饰符确保空对象序列化为{}。副作用是,在某些情况下,::toJsonArray()可能返回一个对象值,而不是从方法名暗示的数组值。

USE_SQUAREALLOW_OBJECT提示是必要的,因为在正常情况下,JsonCodec会尝试使用花括号{}编码所有对象值,在编码结果中插入必要的_type_属性以确保编码的数组永远不会是列表。PHP的json_encode将为非列表数组使用{}表示法。如果您不希望在编码结果中添加_type_属性,则需要指定在模糊情况下是否更喜欢使用[]表示法(USE_SQUARE)或{}表示法(ALLOW_OBJECT)。

带有提示修饰符的示例请参阅tests/SampleList.php及其关联的测试用例。

当可以使用超类编解码器实例化各种子类的对象时,可以使用Hint::INHERITED修饰符。此示例请参阅tests/Pet.phptests/Dog.phptests/Cat.php及其在tests/JsonCodecTest.php中的关联测试用例。

在某些情况下,::jsonClassHintFor()可能不足以描述JSON的隐式类型;例如,带有标签的联合值或嵌套在非同质数组中或深层的隐式类型对象。对于这些用例,::jsonClassCodec()方法提供了一个JsonCodecInterface参数。这允许序列化/反序列化代码手动使用隐式类型对JSON数组的一部分进行编码/解码。更多详细信息请参阅src/JsonCodecInterface.php中的接口文档,以及完整的示例请参阅tests/TaggedValue.php

使用受保护的JsonCodec::isArrayMarked()JsonCodec::markArray()JsonCodec::unmarkArray()方法可以进一步自定义类名和类提示的编码。完整示例请参阅tests/ReservedKeyCodec.php

运行测试

composer install
composer test

历史

JsonCodec概念首次在MediaWiki 1.36.0版本中引入(请参阅dbdc2a3cd33)。在MediaWiki 1.41开发周期中将其从MediaWiki代码库中分离出来,并作为一个独立的库发布,同时进行了API的更改。