< Summary

Class:Microsoft.Azure.Search.Serialization.DocumentConverter
Assembly:Microsoft.Azure.Search.Data
File(s):C:\Git\azure-sdk-for-net\sdk\search\Microsoft.Azure.Search.Data\src\Customizations\Serialization\DocumentConverter.cs
Covered lines:50
Uncovered lines:4
Coverable lines:54
Total lines:179
Line coverage:92.5% (50 of 54)
Covered branches:34
Total branches:41
Branch coverage:82.9% (34 of 41)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.cctor()-100%100%
get_CanRead()-100%100%
get_CanWrite()-0%100%
CanConvert(...)-100%100%
ReadJson(...)-100%100%
WriteJson(...)-0%100%
ConvertToken(...)-100%100%
ConvertArray(...)-94.12%77.42%

File(s)

C:\Git\azure-sdk-for-net\sdk\search\Microsoft.Azure.Search.Data\src\Customizations\Serialization\DocumentConverter.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License. See License.txt in the project root for
 3// license information.
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Linq;
 8using System.Reflection;
 9using Microsoft.Azure.Search.Models;
 10using Microsoft.Spatial;
 11using Newtonsoft.Json;
 12using Newtonsoft.Json.Linq;
 13
 14namespace Microsoft.Azure.Search.Serialization
 15{
 16    /// <summary>
 17    /// Deserializes JSON objects and arrays to .NET types instead of JObject and JArray.
 18    /// </summary>
 19    /// <remarks>
 20    /// This JSON converter supports reading only. When deserializing JSON to an instance of type <c cref="Document">Doc
 21    /// recursively deserialize JSON objects to <c cref="Document">Document</c> instances as well. This includes object 
 22    /// as arrays of objects. It also makes a best-effort attempt to deserialize JSON arrays to a specific .NET array ty
 23    /// arrays are deserialized to arrays of <c cref="System.Object">System.Object</c>.
 24    /// </remarks>
 25    internal class DocumentConverter : JsonConverter
 26    {
 227        private static readonly object[] EmptyObjectArray = new object[0];
 28
 1630429        public override bool CanRead => true;
 30
 031        public override bool CanWrite => false;
 32
 33        public override bool CanConvert(Type objectType) =>
 18415634            typeof(Document).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
 35
 36        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer seriali
 37        {
 1630438            var result = new Document();
 1630439            JObject bag = serializer.Deserialize<JObject>(reader);
 40
 29333641            foreach (JProperty field in bag.Properties())
 42            {
 43                // Skip OData @search annotations. These are deserialized separately.
 13036444                if (field.Name.StartsWith("@search.", StringComparison.Ordinal))
 45                {
 46                    continue;
 47                }
 48
 11426249                object value =
 11426250                    (field.Value is JArray array) ?
 11426251                        ConvertArray(array, serializer) :
 11426252                        ConvertToken(field.Value, serializer);
 53
 11426254                result[field.Name] = value;
 55            }
 56
 1630457            return result;
 58        }
 59
 060        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImple
 61
 62        private static object ConvertToken(JToken token, JsonSerializer serializer)
 63        {
 64            switch (token)
 65            {
 66                case JObject obj:
 24467                    var tokenReader = new JTokenReader(obj);
 24468                    return obj.IsGeoJsonPoint() ?
 24469                        serializer.Deserialize<GeographyPoint>(tokenReader) :
 24470                        (object)serializer.Deserialize<Document>(tokenReader);
 71
 72                default:
 9822673                    return token.ToObject(typeof(object), serializer);
 74            }
 75        }
 76
 77        private static Array ConvertArray(JArray array, JsonSerializer serializer)
 78        {
 1634679            if (array.Count < 1)
 80            {
 81                // With no type information, assume type object.
 1609482                return EmptyObjectArray;
 83            }
 84
 85            Tuple<bool, T> ConvertToReferenceType<T>(JToken token, bool allowNull) where T : class
 86            {
 47887                switch (ConvertToken(token, serializer))
 88                {
 89                    case T typedValue:
 45690                        return Tuple.Create(true, typedValue);
 91
 1092                    case null when allowNull:
 1093                        return Tuple.Create(true, (T)null);
 94
 95                    default:
 1296                        return Tuple.Create(false, (T)null);
 97                }
 98            }
 99
 100            Tuple<bool, T> ConvertToValueType<T>(JToken token) where T : struct
 101            {
 46102                switch (ConvertToken(token, serializer))
 103                {
 104                    case T typedValue:
 46105                        return Tuple.Create(true, typedValue);
 106
 107                    default:
 0108                        return Tuple.Create(false, default(T));
 109                }
 110            }
 111
 112            Array ConvertToArrayOfType<T>(Func<JToken, Tuple<bool, T>> convert)
 113            {
 252114                var typedValues = new T[array.Count];
 115
 116                // Explicit loop ensures only one pass through the array.
 1528117                for (int i = 0; i < typedValues.Length; i++)
 118                {
 524119                    JToken token = array[i];
 120
 121                    // Avoiding ValueTuple for now to avoid taking an extra dependency.
 524122                    Tuple<bool, T> convertResult = convert(token);
 524123                    bool wasConverted = convertResult.Item1;
 524124                    T typedValue = convertResult.Item2;
 125
 524126                    if (wasConverted)
 127                    {
 512128                        typedValues[i] = typedValue;
 129                    }
 130                    else
 131                    {
 132                        // As soon as we see something other than the expected type, give up on the typed array and buil
 12133                        IEnumerable<JToken> remainingTokens = array.Skip(i);
 42134                        IEnumerable<object> remainingObjects = remainingTokens.Select(t => ConvertToken(t, serializer));
 12135                        return typedValues.Take(i).Cast<object>().Concat(remainingObjects).ToArray();
 136                    }
 137                }
 138
 240139                return typedValues;
 140            }
 141
 142            Array ConvertToArrayOfReferenceType<T>(bool allowNull = false) where T : class =>
 712143                ConvertToArrayOfType<T>(t => ConvertToReferenceType<T>(t, allowNull));
 144
 18145            Array ConvertToArrayOfValueType<T>() where T : struct => ConvertToArrayOfType<T>(ConvertToValueType<T>);
 146
 147            // We need to figure out the best-fit type for the array based on the data that's been deserialized so far.
 148            // We can do this by checking the first element. Note that null as a first element is a special case for
 149            // backward compatibility.
 252150            switch (array[0].Type)
 151            {
 152                case JTokenType.Null:
 153                case JTokenType.String:
 154                    // Arrays of all nulls or a mixture of strings and nulls are treated as string arrays for backward c
 186155                    return ConvertToArrayOfReferenceType<string>(allowNull: true);
 156
 157                case JTokenType.Boolean:
 4158                    return ConvertToArrayOfValueType<bool>();
 159
 160                case JTokenType.Integer:
 8161                    return ConvertToArrayOfValueType<long>();
 162
 163                case JTokenType.Float:
 2164                    return ConvertToArrayOfValueType<double>();
 165
 166                case JTokenType.Date:
 4167                    return ConvertToArrayOfValueType<DateTimeOffset>();
 168
 169                case JTokenType.Object:
 48170                    return ((JObject)array[0]).IsGeoJsonPoint() ?
 48171                        ConvertToArrayOfReferenceType<GeographyPoint>() :
 48172                        ConvertToArrayOfReferenceType<Document>();
 173
 174                default:
 0175                    return array.Select(t => ConvertToken(t, serializer)).ToArray();
 176            }
 177        }
 178    }
 179}