diff --git a/dart/packages/fory-test/test/schema_evolution_test/schema_evolution_test.dart b/dart/packages/fory-test/test/schema_evolution_test/schema_evolution_test.dart new file mode 100644 index 0000000000..437c5f1451 --- /dev/null +++ b/dart/packages/fory-test/test/schema_evolution_test/schema_evolution_test.dart @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +library; + +import 'dart:typed_data'; +import 'package:fory/fory.dart'; +import 'package:fory_test/entity/simple_struct1.dart'; +import 'package:test/test.dart'; + +const int _listTypeId = 22; +const int _namedCompatibleStructTypeId = 30; + +void main() { + group('Schema evolution (TypeDef meta share)', () { + test('round-trip with compatible mode and meta share', () { + final fory = Fory(compatible: true, ref: true); + fory.register($SimpleStruct1, typename: 'SimpleStruct1'); + + final a = SimpleStruct1()..a = Int32(100); + final b = SimpleStruct1()..a = Int32(200); + + final bytes = fory.serialize([a, b]); + expect(bytes.isNotEmpty, isTrue); + + final decoded = fory.deserialize(bytes) as List; + expect(decoded.length, 2); + expect(decoded[0], isA()); + expect(decoded[1], isA()); + expect((decoded[0] as SimpleStruct1).a.value, 100); + expect((decoded[1] as SimpleStruct1).a.value, 200); + }); + + test('meta share cache: first occurrence has TypeDef, later use reference', () { + final fory = Fory(compatible: true, ref: true); + fory.register($SimpleStruct1, typename: 'SimpleStruct1'); + + final list = [ + SimpleStruct1()..a = Int32(1), + SimpleStruct1()..a = Int32(2), + SimpleStruct1()..a = Int32(3), + ]; + final bytes = fory.serialize(list); + + final markers = _collectSharedTypeMarkers(bytes); + expect(markers.length, 1, reason: 'List writes element type once'); + expect(markers[0] & 1, 0, reason: 'Only occurrence should be new TypeDef (even marker)'); + + final decoded = fory.deserialize(bytes) as List; + expect(decoded.length, 3); + expect((decoded[0] as SimpleStruct1).a.value, 1); + expect((decoded[1] as SimpleStruct1).a.value, 2); + expect((decoded[2] as SimpleStruct1).a.value, 3); + }); + + test('TypeDef format: type ID and field list round-trip', () { + final fory = Fory(compatible: true, ref: true); + fory.register($SimpleStruct1, typename: 'test.SimpleStruct1'); + + final original = SimpleStruct1()..a = Int32(42); + final bytes = fory.serialize(original); + final decoded = fory.deserialize(bytes) as SimpleStruct1; + + expect(decoded.a.value, 42); + }); + + test('named compatible struct (typename) round-trip', () { + final fory = Fory(compatible: true, ref: true); + fory.register($SimpleStruct1, typename: 'myns.SimpleStruct1'); + + final original = SimpleStruct1()..a = Int32(123); + final bytes = fory.serialize(original); + final decoded = fory.deserialize(bytes) as SimpleStruct1; + expect(decoded.a.value, 123); + }); + + test('compatible struct by user type id round-trip', () { + final fory = Fory(compatible: true, ref: true); + fory.register($SimpleStruct1, typeId: 1000); + + final original = SimpleStruct1()..a = Int32(456); + final bytes = fory.serialize(original); + final decoded = fory.deserialize(bytes) as SimpleStruct1; + expect(decoded.a.value, 456); + }); + + test( + 'cross-language golden: decode bytes from Java/Go/Python with different field order', + () { + expect(true, isTrue, reason: 'Placeholder: add golden file and decode assertion'); + }, + skip: 'Cross-language golden bytes not yet added; use integration test with Java/Go/Python', + ); + }); +} + +List _collectSharedTypeMarkers(Uint8List bytes) { + const int metaSizeMask = 0xFF; + final markers = []; + final br = ByteReader.forBytes(bytes); + br.skip(1); // global header + final refFlag = br.readInt8(); + if (refFlag < 0) return markers; + final typeId = br.readVarUint32Small7(); + if (typeId != _listTypeId) return markers; + final length = br.readVarUint32Small7(); + if (length == 0) return markers; + br.readUint8(); + final elemTypeId = br.readVarUint32Small7(); + if (elemTypeId == _namedCompatibleStructTypeId) { + final marker = br.readVarUint32(); + markers.add(marker); + if ((marker & 1) == 0) { + final id = br.readInt64(); + int size = id & metaSizeMask; + if (size == metaSizeMask) size += br.readVarUint32(); + br.skip(size); + } + } + return markers; +} diff --git a/dart/packages/fory/lib/src/deserialization_context.dart b/dart/packages/fory/lib/src/deserialization_context.dart index c985601538..96fe8bc65e 100644 --- a/dart/packages/fory/lib/src/deserialization_context.dart +++ b/dart/packages/fory/lib/src/deserialization_context.dart @@ -18,6 +18,7 @@ */ import 'package:fory/src/deserialization_dispatcher.dart'; +import 'package:fory/src/meta/field_def.dart'; import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart'; import 'package:fory/src/resolver/deserialization_ref_resolver.dart'; import 'package:fory/src/resolver/type_resolver.dart'; @@ -35,7 +36,9 @@ final class DeserializationContext extends Pack { final Stack typeWrapStack; - const DeserializationContext( + List? currentRemoteFieldDefs; + + DeserializationContext( super.structHashResolver, super.getTagByDartType, this.header, diff --git a/dart/packages/fory/lib/src/deserialization_dispatcher.dart b/dart/packages/fory/lib/src/deserialization_dispatcher.dart index ede6f8d9cc..11a9aef365 100644 --- a/dart/packages/fory/lib/src/deserialization_dispatcher.dart +++ b/dart/packages/fory/lib/src/deserialization_dispatcher.dart @@ -54,7 +54,6 @@ class DeserializationDispatcher { HeaderBrief? header = _foryHeaderSerializer.read(br, conf); if (header == null) return null; typeResolver.resetReadContext(); - DeserializationContext deserializationContext = DeserializationContext( StructHashResolver.inst, typeResolver.getRegisteredTag, @@ -79,7 +78,7 @@ class DeserializationDispatcher { } if (refFlag >= RefFlag.UNTRACKED_NOT_NULL.id) { // must deserialize - TypeInfo typeInfo = pack.typeResolver.readTypeInfo(br); + TypeInfo typeInfo = pack.typeResolver.readTypeInfo(br, pack); int refId = refResolver.reserveId(); Object o = _readByTypeInfo(br, typeInfo, refId, pack); refResolver.setRef(refId, o); @@ -117,10 +116,32 @@ class DeserializationDispatcher { } Object readDynamicWithoutRef(ByteReader br, DeserializationContext pack) { - TypeInfo typeInfo = pack.typeResolver.readTypeInfo(br); + TypeInfo typeInfo = pack.typeResolver.readTypeInfo(br, pack); return _readByTypeInfo(br, typeInfo, -1, pack); } + Object? readByTypeInfo(ByteReader br, TypeInfo typeInfo, int refId, + DeserializationContext pack, + {bool? trackingRefOverride}) { + bool trackingRef = trackingRefOverride ?? typeInfo.serializer.writeRef; + if (trackingRef) { + DeserializationRefResolver refResolver = pack.refResolver; + int refFlag = br.readInt8(); + if (refFlag == RefFlag.NULL.id) return null; + if (refFlag == RefFlag.TRACKED_ALREADY.id) { + int refId = br.readVarUint32Small14(); + return refResolver.getObj(refId); + } + if (refFlag >= RefFlag.UNTRACKED_NOT_NULL.id) { + int actualRefId = refResolver.reserveId(); + Object o = _readByTypeInfo(br, typeInfo, actualRefId, pack); + refResolver.setRef(actualRefId, o); + return o; + } + } + return _readByTypeInfo(br, typeInfo, refId, pack); + } + Object _readByTypeInfo(ByteReader br, TypeInfo typeInfo, int refId, DeserializationContext pack) { switch (typeInfo.objType) { diff --git a/dart/packages/fory/lib/src/meta/field_def.dart b/dart/packages/fory/lib/src/meta/field_def.dart new file mode 100644 index 0000000000..0d38690016 --- /dev/null +++ b/dart/packages/fory/lib/src/meta/field_def.dart @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const int kTagIdUseFieldName = -1; + +class FieldDef { + final String name; + final int tagId; + final bool nullable; + final bool trackingRef; + final RemoteFieldType fieldType; + + const FieldDef({ + required this.name, + required this.tagId, + required this.nullable, + required this.trackingRef, + required this.fieldType, + }); +} + +/// Type of a field as read from TypeDef (supports nested LIST/SET/MAP). +abstract base class RemoteFieldType { + const RemoteFieldType(); + + int get typeId; +} + +/// Simple (non-collection) field type. +final class SimpleRemoteFieldType extends RemoteFieldType { + @override + final int typeId; + + const SimpleRemoteFieldType(this.typeId); +} + +/// LIST or SET: typeId is LIST.id or SET.id, elementType is the element type. +final class ListSetRemoteFieldType extends RemoteFieldType { + @override + final int typeId; + final RemoteFieldType elementType; + + const ListSetRemoteFieldType(this.typeId, this.elementType); +} + +/// MAP: typeId is MAP.id, keyType and valueType are the key/value types. +final class MapRemoteFieldType extends RemoteFieldType { + @override + final int typeId; + final RemoteFieldType keyType; + final RemoteFieldType valueType; + + const MapRemoteFieldType(this.typeId, this.keyType, this.valueType); +} diff --git a/dart/packages/fory/lib/src/meta/spec_wraps/type_spec_wrap.dart b/dart/packages/fory/lib/src/meta/spec_wraps/type_spec_wrap.dart index 3205ecb360..8c7e08632f 100644 --- a/dart/packages/fory/lib/src/meta/spec_wraps/type_spec_wrap.dart +++ b/dart/packages/fory/lib/src/meta/spec_wraps/type_spec_wrap.dart @@ -65,6 +65,17 @@ class TypeSpecWrap { return typeSpecWraps; } + static TypeSpecWrap forRemote(Serializer serializer, {bool nullable = false}) { + return TypeSpecWrap._( + Object, + serializer.objType, + true, + nullable, + const [], + serializer, + ); + } + bool get hasGenericsParam => genericsArgs.isNotEmpty; TypeSpecWrap? get param0 => genericsArgs.isNotEmpty ? genericsArgs[0] : null; diff --git a/dart/packages/fory/lib/src/resolver/impl/type_resolver_impl.dart b/dart/packages/fory/lib/src/resolver/impl/type_resolver_impl.dart index cb2ff6a526..91fdfd8766 100644 --- a/dart/packages/fory/lib/src/resolver/impl/type_resolver_impl.dart +++ b/dart/packages/fory/lib/src/resolver/impl/type_resolver_impl.dart @@ -35,6 +35,7 @@ import 'package:fory/src/exception/registration_exception.dart' import 'package:fory/src/fory_context.dart'; import 'package:fory/src/memory/byte_reader.dart'; import 'package:fory/src/memory/byte_writer.dart'; +import 'package:fory/src/meta/field_def.dart'; import 'package:fory/src/meta/type_info.dart'; import 'package:fory/src/meta/meta_string_byte.dart'; import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart'; @@ -49,6 +50,7 @@ import 'package:fory/src/resolver/spec_lookup.dart'; import 'package:fory/src/resolver/tag_string_resolver.dart'; import 'package:fory/src/resolver/struct_hash_resolver.dart'; import 'package:fory/src/resolver/type_resolver.dart'; +import 'package:fory/src/deserialization_context.dart'; import 'package:fory/src/serializer/class_serializer.dart'; import 'package:fory/src/serializer/enum_serializer.dart'; import 'package:fory/src/serializer/serializer.dart'; @@ -59,6 +61,25 @@ import 'package:fory/src/util/string_util.dart'; import '../../exception/deserialization_exception.dart' show UnsupportedTypeException; +/// Result of parsing a TypeDef body: resolved type and field definitions from the wire. +class _ReadTypeDefResult { + final TypeInfo typeInfo; + final List fieldDefs; + + _ReadTypeDefResult(this.typeInfo, this.fieldDefs); +} + +/// Cached entry for shared type meta: TypeInfo plus remote field list. +class _CachedTypeDef { + final TypeInfo typeInfo; + final List fieldDefs; + + _CachedTypeDef(this.typeInfo, this.fieldDefs); +} + +/// Field name encoding in TypeDef field header: 3 = TAG_ID (no name bytes). +const int _fieldNameEncodingTagId = 3; + final class TypeResolverImpl extends TypeResolver { static const int _metaSizeMask = 0xff; static const int _hasFieldsMetaFlag = 1 << 8; @@ -103,7 +124,7 @@ final class TypeResolverImpl extends TypeResolver { final MetaStringDecoder _typeNameDecoder; final Map _type2Spec; final Map _writeTypeToIndex; - final List _readTypeInfos; + final List<_CachedTypeDef> _readTypeInfos; final Map _typeToEncodedTypeDef; TypeResolverImpl( @@ -116,7 +137,7 @@ final class TypeResolverImpl extends TypeResolver { _typeNameDecoder = Encoders.typeNameDecoder, _type2Spec = HashMap(), _writeTypeToIndex = HashMap(), - _readTypeInfos = [], + _readTypeInfos = <_CachedTypeDef>[], _typeToEncodedTypeDef = HashMap(), _msResolver = MetaStringResolver.newInst, _tstrEncoder = TagStringResolver.newInst, @@ -419,13 +440,16 @@ final class TypeResolverImpl extends TypeResolver { } @override - void resetReadContext() { + void resetReadContext([DeserializationContext? pack]) { _readTypeInfos.clear(); + if (pack != null) { + pack.currentRemoteFieldDefs = null; + } } @override - TypeInfo readTypeInfo(ByteReader br) { - int xtypeId = br.readUint8(); + TypeInfo readTypeInfo(ByteReader br, [DeserializationContext? pack]) { + int xtypeId = br.readVarUint32Small7(); ObjType? xtype = ObjType.fromId(xtypeId); if (xtype == null) { throw UnregisteredTypeException('xtypeId=$xtypeId'); @@ -451,13 +475,13 @@ final class TypeResolverImpl extends TypeResolver { '${xtype.name}(userTypeId=$userTypeId)'); case ObjType.COMPATIBLE_STRUCT: case ObjType.NAMED_COMPATIBLE_STRUCT: - return _readSharedTypeMeta(br); + return _readSharedTypeMeta(br, pack); case ObjType.NAMED_ENUM: case ObjType.NAMED_STRUCT: case ObjType.NAMED_EXT: case ObjType.NAMED_UNION: if (_ctx.conf.compatible) { - return _readSharedTypeMeta(br); + return _readSharedTypeMeta(br, pack); } MetaStringBytes pkgBytes = _msResolver.readMetaStringBytes(br); // assert(pkgBytes.length == 0); // fory dart does not support package @@ -484,6 +508,14 @@ final class TypeResolverImpl extends TypeResolver { } } + @override + TypeInfo? getTypeInfoByObjTypeId(int typeId) { + if (typeId < 0 || typeId >= _ctx.objTypeId2TypeInfo.length) { + return null; + } + return _ctx.objTypeId2TypeInfo[typeId]; + } + TypeInfo _getAndCacheSpecByBytes( LongLongKey key, MetaStringBytes packageBytes, @@ -508,7 +540,7 @@ final class TypeResolverImpl extends TypeResolver { if (typeInfo == null) { throw UnregisteredTypeException(dartType); } - bw.writeUint8(typeInfo.objType.id); + bw.writeVarUint32Small7(typeInfo.objType.id); switch (typeInfo.objType) { case ObjType.ENUM: case ObjType.STRUCT: @@ -538,7 +570,7 @@ final class TypeResolverImpl extends TypeResolver { return typeInfo; } - TypeInfo _readSharedTypeMeta(ByteReader br) { + TypeInfo _readSharedTypeMeta(ByteReader br, [DeserializationContext? pack]) { final int marker = br.readVarUint32(); final bool isRef = (marker & 1) == 1; final int index = marker >>> 1; @@ -547,7 +579,11 @@ final class TypeResolverImpl extends TypeResolver { throw UnregisteredTypeException( 'Shared type index out of bounds: $index'); } - return _readTypeInfos[index]; + final _CachedTypeDef cached = _readTypeInfos[index]; + if (pack != null) { + pack.currentRemoteFieldDefs = cached.fieldDefs; + } + return cached.typeInfo; } final int id = br.readInt64(); final int unsignedId = id & _allBits64Mask; @@ -557,38 +593,137 @@ final class TypeResolverImpl extends TypeResolver { } final Uint8List bodyBytes = br.copyBytes(size); if ((unsignedId & _compressMetaFlag) != 0) { - throw UnregisteredTypeException('Compressed TypeDef is not supported'); + throw UnregisteredTypeException( + 'Compressed TypeDef is not supported; use uncompressed meta for Dart compatibility'); } - final TypeInfo typeInfo = _readTypeInfoFromTypeDefBody(bodyBytes); - _readTypeInfos.add(typeInfo); - return typeInfo; + final _ReadTypeDefResult result = _readTypeInfoFromTypeDefBody(bodyBytes); + _readTypeInfos.add(_CachedTypeDef(result.typeInfo, result.fieldDefs)); + if (pack != null) { + pack.currentRemoteFieldDefs = result.fieldDefs; + } + return result.typeInfo; } - TypeInfo _readTypeInfoFromTypeDefBody(Uint8List bodyBytes) { + _ReadTypeDefResult _readTypeInfoFromTypeDefBody(Uint8List bodyBytes) { final ByteReader bodyReader = ByteReader.forBytes(bodyBytes); int header = bodyReader.readUint8(); int numFields = header & _smallFieldThreshold; if (numFields == _smallFieldThreshold) { numFields += bodyReader.readVarUint32Small7(); } + TypeInfo typeInfo; if ((header & _registerByNameFlag) != 0) { final String namespace = _readPackageName(bodyReader); final String typeName = _readTypeName(bodyReader); final String qualifiedName = StringUtil.addingTypeNameAndNs(namespace, typeName); - final TypeInfo? typeInfo = _ctx.tag2TypeInfo[qualifiedName]; - if (typeInfo == null) { + final TypeInfo? resolved = _ctx.tag2TypeInfo[qualifiedName]; + if (resolved == null) { throw UnregisteredTagException(qualifiedName); } - return typeInfo; + typeInfo = resolved; + } else { + final int typeId = bodyReader.readVarUint32Small7(); + final int userTypeId = bodyReader.readVarUint32(); + final TypeInfo? resolved = _lookupTypeInfoByUserTypeId(typeId, userTypeId); + if (resolved == null) { + throw UnregisteredTypeException( + 'typeId=$typeId,userTypeId=$userTypeId'); + } + typeInfo = resolved; } - final int typeId = bodyReader.readUint8(); - final int userTypeId = bodyReader.readVarUint32(); - final TypeInfo? typeInfo = _lookupTypeInfoByUserTypeId(typeId, userTypeId); - if (typeInfo == null) { - throw UnregisteredTypeException('typeId=$typeId,userTypeId=$userTypeId'); + final List fieldDefs = _readTypeDefFields(bodyReader, numFields); + return _ReadTypeDefResult(typeInfo, fieldDefs); + } + + List _readTypeDefFields(ByteReader br, int numFields) { + final List out = []; + for (int i = 0; i < numFields; i++) { + out.add(_readTypeDefField(br)); + } + return out; + } + + FieldDef _readTypeDefField(ByteReader br) { + final int fieldHeader = br.readUint8(); + final int nameEncoding = (fieldHeader >> 6) & 3; + int size = (fieldHeader >> 2) & 15; + if (size == _fieldNameSizeThreshold) { + size += br.readVarUint32Small7(); + } + final bool nullable = (fieldHeader & 2) != 0; + final bool trackingRef = (fieldHeader & 1) != 0; + final RemoteFieldType fieldType = _readRemoteFieldType(br); + String name = ''; + int tagId = kTagIdUseFieldName; + if (nameEncoding == _fieldNameEncodingTagId) { + tagId = size; + } else { + final int nameBytesLength = size + 1; + final Uint8List nameBytes = br.readBytesView(nameBytesLength); + name = _decodeFieldName(nameBytes, nameEncoding); + } + return FieldDef( + name: name, + tagId: tagId, + nullable: nullable, + trackingRef: trackingRef, + fieldType: fieldType, + ); + } + + String _decodeFieldName(Uint8List bytes, int encodingFlag) { + final MetaStringEncoding encoding = _fieldNameEncodingByFlag(encodingFlag); + return _typeNameDecoder.decode(bytes, encoding); + } + + MetaStringEncoding _fieldNameEncodingByFlag(int flag) { + switch (flag) { + case 0: + return MetaStringEncoding.utf8; + case 1: + return MetaStringEncoding.atls; + case 2: + return MetaStringEncoding.luds; + case _fieldNameEncodingTagId: + throw RegistrationArgumentException( + 'TAG_ID encoding has no name bytes to decode'); + default: + throw RegistrationArgumentException(flag); + } + } + + RemoteFieldType _readRemoteFieldType(ByteReader br) { + final int typeId = br.readVarUint32Small7(); + switch (ObjType.fromId(typeId)) { + case ObjType.LIST: + case ObjType.SET: + final RemoteFieldType elementType = _readNestedFieldType(br); + return ListSetRemoteFieldType(typeId, elementType); + case ObjType.MAP: + final RemoteFieldType keyType = _readNestedFieldType(br); + final RemoteFieldType valueType = _readNestedFieldType(br); + return MapRemoteFieldType(typeId, keyType, valueType); + default: + return SimpleRemoteFieldType(typeId); + } + } + + RemoteFieldType _readNestedFieldType(ByteReader br) { + final int packed = br.readVarUint32Small7(); + final int typeId = packed >> 2; + switch (ObjType.fromId(typeId)) { + case ObjType.LIST: + case ObjType.SET: + final RemoteFieldType elementType = _readNestedFieldType(br); + return ListSetRemoteFieldType(typeId, elementType); + case ObjType.MAP: + final RemoteFieldType keyType = _readNestedFieldType(br); + final RemoteFieldType valueType = _readNestedFieldType(br); + return MapRemoteFieldType(typeId, keyType, valueType); + default: + return SimpleRemoteFieldType(typeId); } - return typeInfo; } TypeInfo? _lookupTypeInfoByUserTypeId(int typeId, int userTypeId) { @@ -710,7 +845,7 @@ final class TypeResolverImpl extends TypeResolver { _writePackageName(writer, ns); _writeTypeName(writer, typeName); } else { - writer.writeUint8(typeInfo.objType.id); + writer.writeVarUint32Small7(typeInfo.objType.id); writer.writeVarUint32(typeInfo.userTypeId); } for (int i = 0; i < fields.length; ++i) { @@ -765,7 +900,7 @@ final class TypeResolverImpl extends TypeResolver { writer.writeUint8(header); } final int typeId = _fieldTypeId(field.typeSpec); - writer.writeUint8(typeId); + writer.writeVarUint32Small7(typeId); _writeNestedTypeInfo(writer, field.typeSpec); writer.writeBytes(encodedName); } diff --git a/dart/packages/fory/lib/src/resolver/type_resolver.dart b/dart/packages/fory/lib/src/resolver/type_resolver.dart index dffeadc794..849e0813df 100644 --- a/dart/packages/fory/lib/src/resolver/type_resolver.dart +++ b/dart/packages/fory/lib/src/resolver/type_resolver.dart @@ -18,6 +18,7 @@ */ import 'package:fory/src/codegen/entity/struct_hash_pair.dart'; +import 'package:fory/src/deserialization_context.dart'; import 'package:fory/src/memory/byte_reader.dart'; import 'package:fory/src/meta/type_info.dart'; import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart'; @@ -68,9 +69,11 @@ abstract base class TypeResolver { void resetWriteContext(); - void resetReadContext(); + void resetReadContext([DeserializationContext? pack]); - TypeInfo readTypeInfo(ByteReader br); + TypeInfo readTypeInfo(ByteReader br, [DeserializationContext? pack]); + + TypeInfo? getTypeInfoByObjTypeId(int typeId); String getRegisteredTag(Type type); diff --git a/dart/packages/fory/lib/src/serializer/class_serializer.dart b/dart/packages/fory/lib/src/serializer/class_serializer.dart index 750dbe2158..77bf2ef856 100644 --- a/dart/packages/fory/lib/src/serializer/class_serializer.dart +++ b/dart/packages/fory/lib/src/serializer/class_serializer.dart @@ -24,15 +24,19 @@ import 'package:fory/src/deserialization_context.dart'; import 'package:fory/src/exception/deserialization_exception.dart'; import 'package:fory/src/memory/byte_reader.dart'; import 'package:fory/src/memory/byte_writer.dart'; +import 'package:fory/src/meta/field_def.dart'; +import 'package:fory/src/meta/type_info.dart'; import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart'; import 'package:fory/src/meta/specs/type_spec.dart'; import 'package:fory/src/meta/specs/field_spec.dart'; import 'package:fory/src/meta/specs/field_sorter.dart'; import 'package:fory/src/resolver/struct_hash_resolver.dart'; +import 'package:fory/src/resolver/type_resolver.dart'; import 'package:fory/src/serializer/custom_serializer.dart'; import 'package:fory/src/serializer/serializer.dart'; import 'package:fory/src/serializer/serializer_cache.dart'; import 'package:fory/src/serialization_context.dart'; +import 'package:fory/src/util/string_util.dart'; final class ClassSerializerCache extends SerializerCache { const ClassSerializerCache(); @@ -123,6 +127,12 @@ final class ClassSerializer extends CustomSerializer { Object obj = _noArgConstruct(); pack.refResolver.setRefTheLatestId( obj); // Need to ref immediately to prevent subsequent circular references and for normal reference tracking + final List? remoteDefs = pack.currentRemoteFieldDefs; + if (_compatible && remoteDefs != null) { + pack.currentRemoteFieldDefs = null; + _readByRemoteFieldOrder(br, obj, remoteDefs, pack, setter: true); + return obj; + } for (int i = 0; i < _fields.length; ++i) { FieldSpec fieldSpec = _fields[i]; if (!fieldSpec.includeFromFory) continue; @@ -218,6 +228,17 @@ final class ClassSerializer extends CustomSerializer { Object _byParameterizedCons( ByteReader br, int refId, DeserializationContext pack) { + final List? remoteDefs = pack.currentRemoteFieldDefs; + if (_compatible && remoteDefs != null) { + pack.currentRemoteFieldDefs = null; + final List args = List.filled(_fields.length, null); + _readByRemoteFieldOrder(br, args, remoteDefs, pack, setter: false); + Object obj = _construct!(args); + if (refId >= 0) { + pack.refResolver.setRef(refId, obj); + } + return obj; + } List args = List.filled(_fields.length, null); for (int i = 0; i < _fields.length; ++i) { FieldSpec fieldSpec = _fields[i]; @@ -260,4 +281,122 @@ final class ClassSerializer extends CustomSerializer { } return obj; } + + void _readByRemoteFieldOrder(ByteReader br, dynamic target, + List remoteDefs, DeserializationContext pack, + {required bool setter}) { + final Map nameToIndex = {}; + for (int i = 0; i < _fields.length; ++i) { + if (!_fields[i].includeFromFory) continue; + final String canonical = + StringUtil.lowerCamelToLowerUnderscore(_fields[i].name); + nameToIndex[canonical] = i; + } + final TypeResolver resolver = pack.typeResolver; + for (final FieldDef remote in remoteDefs) { + final Object? value = + _readFieldValueByRemoteType(br, remote, pack, resolver); + final String name = remote.name; + final int? localIndex = nameToIndex[name]; + if (localIndex != null) { + if (setter) { + assert(_fields[localIndex].setter != null); + _fields[localIndex].setter!(target, value); + } else { + (target as List)[localIndex] = value; + } + } + } + } + + Object? _readFieldValueByRemoteType( + ByteReader br, FieldDef fieldDef, DeserializationContext pack, + TypeResolver resolver) { + final RemoteFieldType rft = fieldDef.fieldType; + if (rft is SimpleRemoteFieldType) { + final TypeInfo? typeInfo = resolver.getTypeInfoByObjTypeId(rft.typeId); + if (typeInfo == null) { + _throwUnsupportedTypeId(rft.typeId); + } + return pack.deserializationDispatcher.readByTypeInfo( + br, typeInfo, -1, pack, + trackingRefOverride: fieldDef.trackingRef); + } + if (rft is ListSetRemoteFieldType) { + final TypeInfo? collectionTypeInfo = + resolver.getTypeInfoByObjTypeId(rft.typeId); + if (collectionTypeInfo == null) { + _throwUnsupportedTypeId(rft.typeId); + } + final TypeInfo? elementTypeInfo = + _typeInfoForRemoteFieldType(rft.elementType, resolver); + if (elementTypeInfo == null) { + _throwUnsupportedTypeId(rft.elementType.typeId); + } + pack.typeWrapStack + .push(TypeSpecWrap.forRemote(elementTypeInfo.serializer, + nullable: fieldDef.nullable)); + try { + return pack.deserializationDispatcher.readByTypeInfo( + br, collectionTypeInfo, -1, pack, + trackingRefOverride: fieldDef.trackingRef); + } finally { + pack.typeWrapStack.pop(); + } + } + if (rft is MapRemoteFieldType) { + final TypeInfo? mapTypeInfo = + resolver.getTypeInfoByObjTypeId(ObjType.MAP.id); + if (mapTypeInfo == null) { + _throwUnsupportedTypeId(ObjType.MAP.id); + } + final TypeInfo? keyTypeInfo = + _typeInfoForRemoteFieldType(rft.keyType, resolver); + final TypeInfo? valueTypeInfo = + _typeInfoForRemoteFieldType(rft.valueType, resolver); + if (keyTypeInfo == null || valueTypeInfo == null) { + _throwUnsupportedTypeId(rft.typeId); + } + pack.typeWrapStack + .push(TypeSpecWrap.forRemote(keyTypeInfo.serializer, + nullable: fieldDef.nullable)); + pack.typeWrapStack + .push(TypeSpecWrap.forRemote(valueTypeInfo.serializer, + nullable: fieldDef.nullable)); + try { + return pack.deserializationDispatcher.readByTypeInfo( + br, mapTypeInfo, -1, pack, + trackingRefOverride: fieldDef.trackingRef); + } finally { + pack.typeWrapStack.pop(); + pack.typeWrapStack.pop(); + } + } + _throwUnsupportedTypeId(rft.typeId); + } + + Never _throwUnsupportedTypeId(int typeId) { + final ObjType? objType = ObjType.fromId(typeId); + if (objType != null) { + throw UnsupportedTypeException(objType); + } + throw ForyMismatchException( + typeId, + -1, + 'Unknown type id from remote TypeDef: $typeId'); + } + + TypeInfo? _typeInfoForRemoteFieldType( + RemoteFieldType rft, TypeResolver resolver) { + if (rft is SimpleRemoteFieldType) { + return resolver.getTypeInfoByObjTypeId(rft.typeId); + } + if (rft is ListSetRemoteFieldType) { + return resolver.getTypeInfoByObjTypeId(rft.typeId); + } + if (rft is MapRemoteFieldType) { + return resolver.getTypeInfoByObjTypeId(ObjType.MAP.id); + } + return resolver.getTypeInfoByObjTypeId(rft.typeId); + } } diff --git a/dart/packages/fory/lib/src/serializer/collection/list/list_serializer.dart b/dart/packages/fory/lib/src/serializer/collection/list/list_serializer.dart index 55e75c43da..182de241fd 100644 --- a/dart/packages/fory/lib/src/serializer/collection/list/list_serializer.dart +++ b/dart/packages/fory/lib/src/serializer/collection/list/list_serializer.dart @@ -65,9 +65,7 @@ abstract base class ListSerializer extends IterableSerializer { serializer = elemWrap.serializer ?? pack.typeResolver.getRegisteredSerializer(elemWrap.type); } - if (serializer == null) { - serializer = pack.typeResolver.readTypeInfo(br).serializer; - } + serializer ??= pack.typeResolver.readTypeInfo(br, pack).serializer; if ((flags & IterableSerializer.trackingRefFlag) == IterableSerializer.trackingRefFlag) { diff --git a/dart/packages/fory/lib/src/serializer/collection/map/map_serializer.dart b/dart/packages/fory/lib/src/serializer/collection/map/map_serializer.dart index 27e698a167..58a8457ee8 100644 --- a/dart/packages/fory/lib/src/serializer/collection/map/map_serializer.dart +++ b/dart/packages/fory/lib/src/serializer/collection/map/map_serializer.dart @@ -100,7 +100,7 @@ abstract base class MapSerializer> keySerializer = keyWrap.serializer ?? pack.typeResolver.getRegisteredSerializer(keyWrap.type); } else { - keySerializer = pack.typeResolver.readTypeInfo(br).serializer; + keySerializer = pack.typeResolver.readTypeInfo(br, pack).serializer; } Serializer valueSerializer; if (valueDeclaredType) { @@ -111,7 +111,7 @@ abstract base class MapSerializer> valueSerializer = valueWrap.serializer ?? pack.typeResolver.getRegisteredSerializer(valueWrap.type); } else { - valueSerializer = pack.typeResolver.readTypeInfo(br).serializer; + valueSerializer = pack.typeResolver.readTypeInfo(br, pack).serializer; } for (int i = 0; i < chunkSize; ++i) { diff --git a/dart/packages/fory/lib/src/serializer/collection/set/set_serializer.dart b/dart/packages/fory/lib/src/serializer/collection/set/set_serializer.dart index a0622c1a53..d4d2f0257f 100644 --- a/dart/packages/fory/lib/src/serializer/collection/set/set_serializer.dart +++ b/dart/packages/fory/lib/src/serializer/collection/set/set_serializer.dart @@ -67,9 +67,7 @@ abstract base class SetSerializer extends IterableSerializer { if (isDeclElemType) { serializer = elemWrap?.serializer; } - if (serializer == null) { - serializer = pack.typeResolver.readTypeInfo(br).serializer; - } + serializer ??= pack.typeResolver.readTypeInfo(br, pack).serializer; if ((flags & IterableSerializer.trackingRefFlag) == IterableSerializer.trackingRefFlag) {