Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add serialization and deserialization of Dictionary<string, string> and Dictionary<string, object> #1589

Open
wants to merge 8 commits into
base: release-7.x
Choose a base branch
from
109 changes: 109 additions & 0 deletions src/Microsoft.OData.Core/DictionaryOfStringObjectTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//---------------------------------------------------------------------
// <copyright file="DictionaryOfStringObjectTypeConverter.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using Microsoft.OData.Edm;
using Microsoft.OData.Json;

namespace Microsoft.OData
{
/// <summary>
/// Handles serialization and deserialization for types derived from Geography.
/// </summary>
internal sealed class DictionaryOfStringObjectTypeConverter : IPrimitiveTypeConverter
{
/// <summary>
/// Write the Atom representation of an instance of a primitive type to an XmlWriter.
/// </summary>
/// <param name="instance">The instance to write.</param>
/// <param name="writer">The Xml writer to use to write the instance.</param>
public void WriteAtom(object instance, XmlWriter writer)
{
throw new NotImplementedException();
}

/// <summary>
/// Write the Atom representation of an instance of a primitive type to an TextWriter.
/// </summary>
/// <param name="instance">The instance to write.</param>
/// <param name="writer">The text writer to use to write the instance.</param>
public void WriteAtom(object instance, TextWriter writer)
{
throw new NotImplementedException();
}

/// <summary>
/// Write the Json Lite representation of an instance of a primitive type to a json writer.
/// </summary>
/// <param name="instance">The instance to write.</param>
/// <param name="jsonWriter">Instance of JsonWriter.</param>
public void WriteJsonLight(object instance, IJsonWriter jsonWriter)
{
var dictionary = (IDictionary<string, object>)instance;

jsonWriter.StartObjectScope();

foreach (var item in dictionary)
{
jsonWriter.WriteName(item.Key);

switch (item.Value)
{
case bool boolValue:
jsonWriter.WriteValue(boolValue);
break;
case float floatValue:
jsonWriter.WriteValue(floatValue);
break;
case short shortValue:
jsonWriter.WriteValue(shortValue);
break;
case long longValue:
jsonWriter.WriteValue(longValue);
break;
case double doubleValue:
jsonWriter.WriteValue(doubleValue);
break;
case Guid guidValue:
jsonWriter.WriteValue(guidValue);
break;
case decimal decimalValue:
jsonWriter.WriteValue(decimalValue);
break;
case DateTimeOffset dateTimeOffsetValue:
jsonWriter.WriteValue(dateTimeOffsetValue);
break;
case TimeSpan timeSpanValue:
jsonWriter.WriteValue(timeSpanValue);
break;
case byte byteValue:
jsonWriter.WriteValue(byteValue);
break;
case sbyte sbyteValue:
jsonWriter.WriteValue(sbyteValue);
break;
case byte[] byteArrayValue:
jsonWriter.WriteValue(byteArrayValue);
break;
case int intValue:
jsonWriter.WriteValue(intValue);
break;
case string stringValue:
jsonWriter.WriteValue(stringValue);
break;
case IDictionary<string, object> dictionaryOfStringObjectValue:
WriteJsonLight(dictionaryOfStringObjectValue, jsonWriter);
break;
}
}

jsonWriter.EndObjectScope();
}
}
}
61 changes: 61 additions & 0 deletions src/Microsoft.OData.Core/DictionaryOfStringStringTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//---------------------------------------------------------------------
// <copyright file="DictionaryOfStringStringTypeConverter.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using Microsoft.OData.Edm;
using Microsoft.OData.Json;

namespace Microsoft.OData
{
/// <summary>
/// Handles serialization and deserialization for types derived from Geography.
/// </summary>
internal sealed class DictionaryOfStringStringTypeConverter : IPrimitiveTypeConverter
{
/// <summary>
/// Write the Atom representation of an instance of a primitive type to an XmlWriter.
/// </summary>
/// <param name="instance">The instance to write.</param>
/// <param name="writer">The Xml writer to use to write the instance.</param>
public void WriteAtom(object instance, XmlWriter writer)
{
throw new NotImplementedException();
}

/// <summary>
/// Write the Atom representation of an instance of a primitive type to an TextWriter.
/// </summary>
/// <param name="instance">The instance to write.</param>
/// <param name="writer">The text writer to use to write the instance.</param>
public void WriteAtom(object instance, TextWriter writer)
{
throw new NotImplementedException();
}

/// <summary>
/// Write the Json Lite representation of an instance of a primitive type to a json writer.
/// </summary>
/// <param name="instance">The instance to write.</param>
/// <param name="jsonWriter">Instance of JsonWriter.</param>
public void WriteJsonLight(object instance, IJsonWriter jsonWriter)
{
var dictionary = (IDictionary<string, string>)instance;

jsonWriter.StartObjectScope();

foreach (var item in dictionary)
{
jsonWriter.WriteName(item.Key);
jsonWriter.WriteValue(item.Value);
}

jsonWriter.EndObjectScope();
}
}
}
6 changes: 6 additions & 0 deletions src/Microsoft.OData.Core/Json/JsonWriterExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ internal static void WritePrimitiveValue(this IJsonWriter jsonWriter, object val
return;
}

if (value is Dictionary<string, object>)
{
jsonWriter.WriteJsonObjectValue((Dictionary<string, object>)value, null);
return;
}

throw new ODataException(ODataErrorStrings.ODataJsonWriter_UnsupportedValueType(value.GetType().FullName));
}

Expand Down
78 changes: 78 additions & 0 deletions src/Microsoft.OData.Core/Json/ODataJsonReaderCoreUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,84 @@ internal static ISpatial ReadSpatialValue(
return spatialValue;
}

internal static IDictionary<string, object> ReadDictionaryOfStringObjectValue(
IJsonReader jsonReader,
bool insideJsonObjectValue,
ODataInputContext inputContext,
IEdmPrimitiveTypeReference expectedValueTypeReference,
bool validateNullValue,
int recursionDepth,
string propertyName)
{
Debug.Assert(jsonReader != null, "jsonReader != null");
Debug.Assert(inputContext != null, "inputContext != null");
Debug.Assert(expectedValueTypeReference != null, "expectedValueTypeReference != null");
Debug.Assert(expectedValueTypeReference.IsDictionaryOfStringObject(), "ReadDictionaryOfStringObjectValue must be called only with dictionary types");

// Dictionary of String, Object value can be either null constant or a JSON object
// If it's a null primitive value, report a null value.
if (!insideJsonObjectValue && TryReadNullValue(jsonReader, inputContext, expectedValueTypeReference, validateNullValue, propertyName))
{
return null;
}

IDictionary<string, object> dictionaryOfStringObjectValue = new Dictionary<string, object>();
if (insideJsonObjectValue || jsonReader.NodeType == JsonNodeType.StartObject)
{
IDictionary<string, object> jsonObject = ReadObjectValue(jsonReader, insideJsonObjectValue, inputContext, recursionDepth);
foreach (var item in jsonObject)
{
dictionaryOfStringObjectValue[item.Key] = item.Value;
}
}

if (dictionaryOfStringObjectValue == null)
{
throw new ODataException(ODataErrorStrings.ODataJsonReaderCoreUtils_CannotReadDictionaryPropertyValue);
}

return dictionaryOfStringObjectValue;
}

internal static IDictionary<string, string> ReadDictionaryOfStringStringValue(
IJsonReader jsonReader,
bool insideJsonObjectValue,
ODataInputContext inputContext,
IEdmPrimitiveTypeReference expectedValueTypeReference,
bool validateNullValue,
int recursionDepth,
string propertyName)
{
Debug.Assert(jsonReader != null, "jsonReader != null");
Debug.Assert(inputContext != null, "inputContext != null");
Debug.Assert(expectedValueTypeReference != null, "expectedValueTypeReference != null");
Debug.Assert(expectedValueTypeReference.IsDictionaryOfStringString(), "ReadDictionaryOfStringStringValue must be called only with dictionary types");

// Dictionary of String, String value can be either null constant or a JSON object
// If it's a null primitive value, report a null value.
if (!insideJsonObjectValue && TryReadNullValue(jsonReader, inputContext, expectedValueTypeReference, validateNullValue, propertyName))
{
return null;
}

IDictionary<string, string> dictionaryOfStringStringValue = new Dictionary<string, string>();
if (insideJsonObjectValue || jsonReader.NodeType == JsonNodeType.StartObject)
{
IDictionary<string, object> jsonObject = ReadObjectValue(jsonReader, insideJsonObjectValue, inputContext, recursionDepth);
foreach (var item in jsonObject)
{
dictionaryOfStringStringValue[item.Key] = (string)item.Value;
}
}

if (dictionaryOfStringStringValue == null)
{
throw new ODataException(ODataErrorStrings.ODataJsonReaderCoreUtils_CannotReadDictionaryPropertyValue);
}

return dictionaryOfStringStringValue;
}

/// <summary>
/// Tries to read a null value from the JSON reader.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,28 @@ private object ReadPrimitiveValue(bool insideJsonObjectValue, IEdmPrimitiveTypeR
this.recursionDepth,
propertyName);
}
else if (expectedValueTypeReference != null && expectedValueTypeReference.IsDictionaryOfStringObject())
{
result = ODataJsonReaderCoreUtils.ReadDictionaryOfStringObjectValue(
this.JsonReader,
insideJsonObjectValue,
this.JsonLightInputContext,
expectedValueTypeReference,
validateNullValue,
this.recursionDepth,
propertyName);
}
else if (expectedValueTypeReference != null && expectedValueTypeReference.IsDictionaryOfStringString())
{
result = ODataJsonReaderCoreUtils.ReadDictionaryOfStringStringValue(
this.JsonReader,
insideJsonObjectValue,
this.JsonLightInputContext,
expectedValueTypeReference,
validateNullValue,
this.recursionDepth,
propertyName);
}
else
{
if (insideJsonObjectValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ public virtual void WritePrimitiveValue(

value = converter.ConvertToPayloadValue(value, expectedTypeReference);

if (actualTypeReference != null && actualTypeReference.IsSpatial())
if (actualTypeReference != null && actualTypeReference.IsSpatial() || actualTypeReference.IsDictionaryOfStringObject() || actualTypeReference.IsDictionaryOfStringString())
{
PrimitiveConverter.Instance.WriteJsonLight(value, this.JsonWriter);
}
Expand Down
24 changes: 21 additions & 3 deletions src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ internal static class EdmLibraryExtensions
/// <summary>Type reference for Edm.Decimal.</summary>
private static readonly EdmPrimitiveTypeReference DecimalTypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Decimal), false);

/// <summary>Type reference for Edm.DictionaryOfStringObject.</summary>
private static readonly EdmPrimitiveTypeReference DictionaryOfStringObjectTypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DictionaryOfStringObject), true);

/// <summary>Type reference for Edm.DictionaryOfStringString.</summary>
private static readonly EdmPrimitiveTypeReference DictionaryOfStringStringTypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DictionaryOfStringString), true);

/// <summary>Type reference for Edm.Double.</summary>
private static readonly EdmPrimitiveTypeReference DoubleTypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Double), false);

Expand Down Expand Up @@ -94,6 +100,8 @@ static EdmLibraryExtensions()
PrimitiveTypeReferenceMap.Add(typeof(Boolean), BooleanTypeReference);
PrimitiveTypeReferenceMap.Add(typeof(Byte), ByteTypeReference);
PrimitiveTypeReferenceMap.Add(typeof(Decimal), DecimalTypeReference);
PrimitiveTypeReferenceMap.Add(typeof(Dictionary<string, object>), DictionaryOfStringObjectTypeReference);
PrimitiveTypeReferenceMap.Add(typeof(Dictionary<string, string>), DictionaryOfStringStringTypeReference);
PrimitiveTypeReferenceMap.Add(typeof(Double), DoubleTypeReference);
PrimitiveTypeReferenceMap.Add(typeof(Int16), Int16TypeReference);
PrimitiveTypeReferenceMap.Add(typeof(Int32), Int32TypeReference);
Expand Down Expand Up @@ -601,7 +609,7 @@ internal static bool IsPrimitiveType(Type clrType)
return true;
}

return PrimitiveTypeReferenceMap.ContainsKey(clrType) || typeof(ISpatial).IsAssignableFrom(clrType);
return PrimitiveTypeReferenceMap.ContainsKey(clrType) || typeof(ISpatial).IsAssignableFrom(clrType) || typeof(IDictionary).IsAssignableFrom(clrType);
}

/// <summary>
Expand Down Expand Up @@ -1474,7 +1482,15 @@ internal static IEdmPrimitiveTypeReference GetPrimitiveTypeReference(Type clrTyp

// If it didn't work, try spatial types which need assignability.
IEdmPrimitiveType primitiveType = null;
if (typeof(GeographyPoint).IsAssignableFrom(clrType))
if (typeof(IDictionary<string, object>).IsAssignableFrom(clrType))
{
primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DictionaryOfStringObject);
}
else if (typeof(IDictionary<string, string>).IsAssignableFrom(clrType))
{
primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DictionaryOfStringString);
}
else if (typeof(GeographyPoint).IsAssignableFrom(clrType))
{
primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeographyPoint);
}
Expand Down Expand Up @@ -2001,6 +2017,9 @@ private static EdmPrimitiveTypeReference ToTypeReference(IEdmPrimitiveType primi
{
case EdmPrimitiveTypeKind.Boolean:
case EdmPrimitiveTypeKind.Byte:
case EdmPrimitiveTypeKind.Date:
case EdmPrimitiveTypeKind.DictionaryOfStringObject:
case EdmPrimitiveTypeKind.DictionaryOfStringString:
case EdmPrimitiveTypeKind.Double:
case EdmPrimitiveTypeKind.Guid:
case EdmPrimitiveTypeKind.Int16:
Expand All @@ -2009,7 +2028,6 @@ private static EdmPrimitiveTypeReference ToTypeReference(IEdmPrimitiveType primi
case EdmPrimitiveTypeKind.SByte:
case EdmPrimitiveTypeKind.Single:
case EdmPrimitiveTypeKind.Stream:
case EdmPrimitiveTypeKind.Date:
case EdmPrimitiveTypeKind.PrimitiveType:
return new EdmPrimitiveTypeReference(primitiveType, nullable);
case EdmPrimitiveTypeKind.Binary:
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.OData.Core/Microsoft.OData.Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ internal sealed class TextRes {
internal const string ODataJsonLightPropertyAndValueDeserializer_CollectionTypeNotExpected = "ODataJsonLightPropertyAndValueDeserializer_CollectionTypeNotExpected";
internal const string ODataJsonLightPropertyAndValueDeserializer_CollectionTypeExpected = "ODataJsonLightPropertyAndValueDeserializer_CollectionTypeExpected";
internal const string ODataJsonReaderCoreUtils_CannotReadSpatialPropertyValue = "ODataJsonReaderCoreUtils_CannotReadSpatialPropertyValue";
internal const string ODataJsonReaderCoreUtils_CannotReadDictionaryPropertyValue = "ODataJsonReaderCoreUtils_CannotReadDictionaryPropertyValue";
internal const string ODataJsonLightReader_UnexpectedPrimitiveValueForODataResource = "ODataJsonLightReader_UnexpectedPrimitiveValueForODataResource";
internal const string ODataJsonLightReaderUtils_AnnotationWithNullValue = "ODataJsonLightReaderUtils_AnnotationWithNullValue";
internal const string ODataJsonLightReaderUtils_InvalidValueForODataNullAnnotation = "ODataJsonLightReaderUtils_InvalidValueForODataNullAnnotation";
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.OData.Core/Microsoft.OData.Core.txt
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,8 @@ ODataJsonLightPropertyAndValueDeserializer_CollectionTypeExpected=A non-collecti

ODataJsonReaderCoreUtils_CannotReadSpatialPropertyValue=The value specified for the spatial property was not valid. You must specify a valid spatial value.

ODataJsonReaderCoreUtils_CannotReadDictionaryPropertyValue=The value specified for the dictionary property was not valid. You must specify a valid dictionary value.

ODataJsonLightReader_UnexpectedPrimitiveValueForODataResource=If a primitive value is representing a resource, the resource must be null.
ODataJsonLightReaderUtils_AnnotationWithNullValue=The '{0}' instance or property annotation has a null value. In OData, the '{0}' instance or property annotation must have a non-null string value.
ODataJsonLightReaderUtils_InvalidValueForODataNullAnnotation=An '{0}' annotation was found with an invalid value. In OData, the only valid value for the '{0}' annotation is '{1}'.
Expand Down
Loading