using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.Reflection; using System.ServiceModel; using System.Collections; namespace System.Runtime.Serialization.Json { #if USE_REFEMIT public class XmlObjectSerializerWriteContextComplexJson : XmlObjectSerializerWriteContextComplex #else internal class XmlObjectSerializerWriteContextComplexJson : XmlObjectSerializerWriteContextComplex #endif { EmitTypeInformation emitXsiType; bool perCallXsiTypeAlreadyEmitted; bool useSimpleDictionaryFormat; public XmlObjectSerializerWriteContextComplexJson(DataContractJsonSerializer serializer, DataContract rootTypeDataContract) : base(serializer, serializer.MaxItemsInObjectGraph, new StreamingContext(StreamingContextStates.All), serializer.IgnoreExtensionDataObject) { this.emitXsiType = serializer.EmitTypeInformation; this.rootTypeDataContract = rootTypeDataContract; this.serializerKnownTypeList = serializer.knownTypeList; this.dataContractSurrogate = serializer.DataContractSurrogate; this.serializeReadOnlyTypes = serializer.SerializeReadOnlyTypes; this.useSimpleDictionaryFormat = serializer.UseSimpleDictionaryFormat; } internal static XmlObjectSerializerWriteContextComplexJson CreateContext(DataContractJsonSerializer serializer, DataContract rootTypeDataContract) { return new XmlObjectSerializerWriteContextComplexJson(serializer, rootTypeDataContract); } internal IList SerializerKnownTypeList { get { return this.serializerKnownTypeList; } } public bool UseSimpleDictionaryFormat { get { return this.useSimpleDictionaryFormat; } } internal override bool WriteClrTypeInfo(XmlWriterDelegator xmlWriter, Type dataContractType, string clrTypeName, string clrAssemblyName) { return false; } internal override bool WriteClrTypeInfo(XmlWriterDelegator xmlWriter, DataContract dataContract) { return false; } internal override void WriteArraySize(XmlWriterDelegator xmlWriter, int size) { } protected override void WriteTypeInfo(XmlWriterDelegator writer, string dataContractName, string dataContractNamespace) { if (this.emitXsiType != EmitTypeInformation.Never) { if (string.IsNullOrEmpty(dataContractNamespace)) { WriteTypeInfo(writer, dataContractName); } else { WriteTypeInfo(writer, string.Concat(dataContractName, JsonGlobals.NameValueSeparatorString, TruncateDefaultDataContractNamespace(dataContractNamespace))); } } } internal static string TruncateDefaultDataContractNamespace(string dataContractNamespace) { if (!string.IsNullOrEmpty(dataContractNamespace)) { if (dataContractNamespace[0] == '#') { return string.Concat("\\", dataContractNamespace); } else if (dataContractNamespace[0] == '\\') { return string.Concat("\\", dataContractNamespace); } else if (dataContractNamespace.StartsWith(Globals.DataContractXsdBaseNamespace, StringComparison.Ordinal)) { return string.Concat("#", dataContractNamespace.Substring(JsonGlobals.DataContractXsdBaseNamespaceLength)); } } return dataContractNamespace; } static bool RequiresJsonTypeInfo(DataContract contract) { return (contract is ClassDataContract); } void WriteTypeInfo(XmlWriterDelegator writer, string typeInformation) { writer.WriteAttributeString(null, JsonGlobals.serverTypeString, null, typeInformation); } protected override bool WriteTypeInfo(XmlWriterDelegator writer, DataContract contract, DataContract declaredContract) { if (!((object.ReferenceEquals(contract.Name, declaredContract.Name) && object.ReferenceEquals(contract.Namespace, declaredContract.Namespace)) || (contract.Name.Value == declaredContract.Name.Value && contract.Namespace.Value == declaredContract.Namespace.Value)) && (contract.UnderlyingType != Globals.TypeOfObjectArray) && (this.emitXsiType != EmitTypeInformation.Never)) { // We always deserialize collections assigned to System.Object as object[] // Because of its common and JSON-specific nature, // we don't want to validate known type information for object[] // Don't validate known type information when emitXsiType == Never because // known types are not used without type information in the JSON if (RequiresJsonTypeInfo(contract)) { perCallXsiTypeAlreadyEmitted = true; WriteTypeInfo(writer, contract.Name.Value, contract.Namespace.Value); } else { // check if the declared type is System.Enum and throw because // __type information cannot be written for enums since it results in invalid JSON. // Without __type, the resulting JSON cannot be deserialized since a number cannot be directly assigned to System.Enum. if (declaredContract.UnderlyingType == typeof(Enum)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SerializationException (SR.GetString(SR.EnumTypeNotSupportedByDataContractJsonSerializer, declaredContract.UnderlyingType))); } } // Return true regardless of whether we actually wrote __type information // E.g. We don't write __type information for enums, but we still want verifyKnownType // to be true for them. return true; } return false; } #if USE_REFEMIT public void WriteJsonISerializable(XmlWriterDelegator xmlWriter, ISerializable obj) #else internal void WriteJsonISerializable(XmlWriterDelegator xmlWriter, ISerializable obj) #endif { Type objType = obj.GetType(); SerializationInfo serInfo = new SerializationInfo(objType, XmlObjectSerializer.FormatterConverter); GetObjectData(obj, serInfo, GetStreamingContext()); if (DataContract.GetClrTypeFullName(objType) != serInfo.FullTypeName) { throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.GetString(SR.ChangingFullTypeNameNotSupported, serInfo.FullTypeName, DataContract.GetClrTypeFullName(objType)))); } else { base.WriteSerializationInfo(xmlWriter, objType, serInfo); } } #if USE_REFEMIT public static DataContract GetRevisedItemContract(DataContract oldItemContract) #else internal static DataContract GetRevisedItemContract(DataContract oldItemContract) #endif { if ((oldItemContract != null) && oldItemContract.UnderlyingType.IsGenericType && (oldItemContract.UnderlyingType.GetGenericTypeDefinition() == Globals.TypeOfKeyValue)) { return DataContract.GetDataContract(oldItemContract.UnderlyingType); } return oldItemContract; } protected override void WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, object obj, RuntimeTypeHandle declaredTypeHandle) { JsonDataContract jsonDataContract = JsonDataContract.GetJsonDataContract(dataContract); if (emitXsiType == EmitTypeInformation.Always && !perCallXsiTypeAlreadyEmitted && RequiresJsonTypeInfo(dataContract)) { WriteTypeInfo(xmlWriter, jsonDataContract.TypeName); } perCallXsiTypeAlreadyEmitted = false; DataContractJsonSerializer.WriteJsonValue(jsonDataContract, xmlWriter, obj, this, declaredTypeHandle); } protected override void WriteNull(XmlWriterDelegator xmlWriter) { DataContractJsonSerializer.WriteJsonNull(xmlWriter); } #if USE_REFEMIT public XmlDictionaryString CollectionItemName #else internal XmlDictionaryString CollectionItemName #endif { get { return JsonGlobals.itemDictionaryString; } } #if USE_REFEMIT public static void WriteJsonNameWithMapping(XmlWriterDelegator xmlWriter, XmlDictionaryString[] memberNames, int index) #else internal static void WriteJsonNameWithMapping(XmlWriterDelegator xmlWriter, XmlDictionaryString[] memberNames, int index) #endif { xmlWriter.WriteStartElement("a", JsonGlobals.itemString, JsonGlobals.itemString); xmlWriter.WriteAttributeString(null, JsonGlobals.itemString, null, memberNames[index].Value); } internal override void WriteExtensionDataTypeInfo(XmlWriterDelegator xmlWriter, IDataNode dataNode) { Type dataType = dataNode.DataType; if (dataType == Globals.TypeOfClassDataNode || dataType == Globals.TypeOfISerializableDataNode) { xmlWriter.WriteAttributeString(null, JsonGlobals.typeString, null, JsonGlobals.objectString); base.WriteExtensionDataTypeInfo(xmlWriter, dataNode); } else if (dataType == Globals.TypeOfCollectionDataNode) { xmlWriter.WriteAttributeString(null, JsonGlobals.typeString, null, JsonGlobals.arrayString); // Don't write __type for collections } else if (dataType == Globals.TypeOfXmlDataNode) { // Don't write type or __type for XML types because we serialize them to strings } else if ((dataType == Globals.TypeOfObject) && (dataNode.Value != null)) { DataContract dc = GetDataContract(dataNode.Value.GetType()); if (RequiresJsonTypeInfo(dc)) { base.WriteExtensionDataTypeInfo(xmlWriter, dataNode); } } } protected override void SerializeWithXsiType(XmlWriterDelegator xmlWriter, object obj, RuntimeTypeHandle objectTypeHandle, Type objectType, int declaredTypeID, RuntimeTypeHandle declaredTypeHandle, Type declaredType) { DataContract dataContract; bool verifyKnownType = false; bool isDeclaredTypeInterface = declaredType.IsInterface; if (isDeclaredTypeInterface && CollectionDataContract.IsCollectionInterface(declaredType)) { dataContract = GetDataContract(declaredTypeHandle, declaredType); } else if (declaredType.IsArray) // If declared type is array do not write __serverType. Instead write__serverType for each item { dataContract = GetDataContract(declaredTypeHandle, declaredType); } else { dataContract = GetDataContract(objectTypeHandle, objectType); DataContract declaredTypeContract = (declaredTypeID >= 0) ? GetDataContract(declaredTypeID, declaredTypeHandle) : GetDataContract(declaredTypeHandle, declaredType); verifyKnownType = WriteTypeInfo(xmlWriter, dataContract, declaredTypeContract); HandleCollectionAssignedToObject(declaredType, ref dataContract, ref obj, ref verifyKnownType); } if (isDeclaredTypeInterface) { VerifyObjectCompatibilityWithInterface(dataContract, obj, declaredType); } SerializeAndVerifyType(dataContract, xmlWriter, obj, verifyKnownType, declaredType.TypeHandle, declaredType); } static void VerifyObjectCompatibilityWithInterface(DataContract contract, object graph, Type declaredType) { Type contractType = contract.GetType(); if ((contractType == typeof(XmlDataContract)) && !Globals.TypeOfIXmlSerializable.IsAssignableFrom(declaredType)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.GetString(SR.XmlObjectAssignedToIncompatibleInterface, graph.GetType(), declaredType))); } if ((contractType == typeof(CollectionDataContract)) && !CollectionDataContract.IsCollectionInterface(declaredType)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.GetString(SR.CollectionAssignedToIncompatibleInterface, graph.GetType(), declaredType))); } } void HandleCollectionAssignedToObject(Type declaredType, ref DataContract dataContract, ref object obj, ref bool verifyKnownType) { if ((declaredType != dataContract.UnderlyingType) && (dataContract is CollectionDataContract)) { if (verifyKnownType) { VerifyType(dataContract, declaredType); verifyKnownType = false; } if (((CollectionDataContract)dataContract).Kind == CollectionKind.Dictionary) { // Convert non-generic dictionary to generic dictionary IDictionary dictionaryObj = obj as IDictionary; Dictionary genericDictionaryObj = new Dictionary(); foreach (DictionaryEntry entry in dictionaryObj) { genericDictionaryObj.Add(entry.Key, entry.Value); } obj = genericDictionaryObj; } dataContract = GetDataContract(Globals.TypeOfIEnumerable); } } internal override void SerializeWithXsiTypeAtTopLevel(DataContract dataContract, XmlWriterDelegator xmlWriter, object obj, RuntimeTypeHandle originalDeclaredTypeHandle, Type graphType) { bool verifyKnownType = false; Type declaredType = rootTypeDataContract.UnderlyingType; bool isDeclaredTypeInterface = declaredType.IsInterface; if (!(isDeclaredTypeInterface && CollectionDataContract.IsCollectionInterface(declaredType)) && !declaredType.IsArray)//Array covariance is not supported in XSD. If declared type is array do not write xsi:type. Instead write xsi:type for each item { verifyKnownType = WriteTypeInfo(xmlWriter, dataContract, rootTypeDataContract); HandleCollectionAssignedToObject(declaredType, ref dataContract, ref obj, ref verifyKnownType); } if (isDeclaredTypeInterface) { VerifyObjectCompatibilityWithInterface(dataContract, obj, declaredType); } SerializeAndVerifyType(dataContract, xmlWriter, obj, verifyKnownType, declaredType.TypeHandle, declaredType); } void VerifyType(DataContract dataContract, Type declaredType) { bool knownTypesAddedInCurrentScope = false; if (dataContract.KnownDataContracts != null) { scopedKnownTypes.Push(dataContract.KnownDataContracts); knownTypesAddedInCurrentScope = true; } if (!IsKnownType(dataContract, declaredType)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.GetString(SR.DcTypeNotFoundOnSerialize, DataContract.GetClrTypeFullName(dataContract.UnderlyingType), dataContract.StableName.Name, dataContract.StableName.Namespace))); } if (knownTypesAddedInCurrentScope) { scopedKnownTypes.Pop(); } } internal override DataContract GetDataContract(RuntimeTypeHandle typeHandle, Type type) { DataContract dataContract = base.GetDataContract(typeHandle, type); DataContractJsonSerializer.CheckIfTypeIsReference(dataContract); return dataContract; } internal override DataContract GetDataContractSkipValidation(int typeId, RuntimeTypeHandle typeHandle, Type type) { DataContract dataContract = base.GetDataContractSkipValidation(typeId, typeHandle, type); DataContractJsonSerializer.CheckIfTypeIsReference(dataContract); return dataContract; } internal override DataContract GetDataContract(int id, RuntimeTypeHandle typeHandle) { DataContract dataContract = base.GetDataContract(id, typeHandle); DataContractJsonSerializer.CheckIfTypeIsReference(dataContract); return dataContract; } internal static DataContract ResolveJsonDataContractFromRootDataContract(XmlObjectSerializerContext context, XmlQualifiedName typeQName, DataContract rootTypeDataContract) { if (rootTypeDataContract.StableName == typeQName) return rootTypeDataContract; CollectionDataContract collectionContract = rootTypeDataContract as CollectionDataContract; while (collectionContract != null) { DataContract itemContract; if (collectionContract.ItemType.IsGenericType && collectionContract.ItemType.GetGenericTypeDefinition() == typeof(KeyValue<,>)) { itemContract = context.GetDataContract(Globals.TypeOfKeyValuePair.MakeGenericType(collectionContract.ItemType.GetGenericArguments())); } else { itemContract = context.GetDataContract(context.GetSurrogatedType(collectionContract.ItemType)); } if (itemContract.StableName == typeQName) { return itemContract; } collectionContract = itemContract as CollectionDataContract; } return null; } protected override DataContract ResolveDataContractFromRootDataContract(XmlQualifiedName typeQName) { return XmlObjectSerializerWriteContextComplexJson.ResolveJsonDataContractFromRootDataContract(this, typeQName, rootTypeDataContract); } } }