【问题标题】:Transforming Microsoft Graph ListItem Output to a corresponding C# type将 Microsoft Graph ListItem 输出转换为相应的 C# 类型
【发布时间】:2023-12-26 04:18:01
【问题描述】:

通过 SharePoint 和 MS Graph 提供的“帮助”组合,将 JSON 数据转换为类型化数据模型的工作似乎变得更加复杂。 :-)

我在 Microsoft 365 中有一个 SharePoint 列表,我通过 C# 中的图形 API 访问它,其中查询目标是一个类型化的类,其属性与 SharePoint 列表列属性相同。

ListItem 类 Graph API 在 Fields.AdditionalData 类型的 Dictionary<string,object{System.Text.Json.JsonElement}> 中返回结果,它需要变成 IEnumerable<DataItem>,我可以通过 Serialize/Deserialize 从查询结果中获取 List往返,如下:

var backToJSON = ListItems.Select(o => System.Text.Json.JsonSerializer.Serialize(o.Fields.AdditionalData));
var stronglyTypedItems = backToJSON.Select(jsonO => System.Text.Json.JsonSerializer.Deserialize<DataItem>(jsonO));

有没有办法做到这一点,无论是使用更智能的 OData 还是我没见过的 Graph API 中的东西,而不需要使用过去的 JSON 并通过 JSON 序列化器将其发送回两次?强>

更多详情如下: 来自 Graph Explorer 的示例输出 JSON,其中 value 包含一个数组:

"value" : [ 
    { "id": "1001, 
      "fields": { 
        "Column" : "true", 
        "Column2" : "value2", 
        "Column3" : "65" 
      } 
    }, 
    { "id": "1002, 
      "fields": { 
  <and so forth until the array terminates>
  ]
}

对应的 C# 类(字面意思是使用“将 JSON 粘贴为类”构建的):

Public class DataItem {
  public bool Column {get; set;}
  public string Column2 {get; set;}
  public int Column3 {get; set;}
}

C# Graph API 中的“Helper”类提供大部分转换为我实际需要的字段数组:

        private static GraphServiceClient graphClient;

        public static IListItemsCollectionRequest LicenseExpirationsList => graphClient
            .Sites["<guid>"]
            .Lists["<nameOfList>"].Items
            .Request()
            .Header("Accept", "application/json;odata.metadata=none")
            .Select("fields,id")
            .Expand("fields");

            var ListItems = (await GraphHelper.LicenseExpirationsList.GetAsync()).CurrentPage;


// JSON round tripping through JSONSerializer to get the strong type...
// But why? ListItems.Fields.AdditionalData is a Dictionary of JSON elements in the first place!

            var backToJSON = ListItems.Select(o => System.Text.Json.JsonSerializer.Serialize(o.Fields.AdditionalData));
            var stronglyTypedItems = backToJSON.Select(jsonO => System.Text.Json.JsonSerializer.Deserialize<DataItem>(jsonO));
 

            return stronglyTypedItems;

【问题讨论】:

    标签: c# json microsoft-graph-api sharepoint-online sharepoint-list


    【解决方案1】:

    您可以customize the client's JSON serialization 返回默认FieldValueSet 的派生类型。

    首先,定义你自己的扩展FieldValueSet

    public class FieldValueSetWithDataItem : FieldValueSet
    {
        public bool Column { get; set; }
        public string Column2 { get; set; }
        public int Column3 { get; set; }
    }
    

    第二,implement your own JSON converter

    class CustomFieldValueSetJsonConverter : JsonConverter<FieldValueSet>
    {
        private static readonly JsonEncodedText ODataTypeProperty 
            = JsonEncodedText.Encode("@odata.type");
        private static readonly JsonEncodedText IdProperty 
            = JsonEncodedText.Encode("id");
        private static readonly JsonEncodedText ColumnProperty 
            = JsonEncodedText.Encode("Column");
        private static readonly JsonEncodedText Column2Property 
            = JsonEncodedText.Encode("Column2");
        private static readonly JsonEncodedText Column3Property
            = JsonEncodedText.Encode("Column3");
    
        public override FieldValueSet Read(ref Utf8JsonReader reader,
            Type typeToConvert, JsonSerializerOptions options)
        {
            var result = new FieldValueSetWithDataItem();
            using var doc = JsonDocument.ParseValue(ref reader);
            var root = doc.RootElement;
    
            foreach (var element in root.EnumerateObject())
            {
                if (element.NameEquals(ODataTypeProperty.EncodedUtf8Bytes))
                {
                    result.ODataType = element.Value.GetString();
                }
                else if (element.NameEquals(IdProperty.EncodedUtf8Bytes))
                {
                    result.Id = element.Value.GetString();
                }
                else if (element.NameEquals(ColumnProperty.EncodedUtf8Bytes))
                {
                    result.Column = element.Value.GetBoolean();
                }
                else if (element.NameEquals(Column2Property.EncodedUtf8Bytes))
                {
                    result.Column2 = element.Value.GetString();
                }
                else if (element.NameEquals(Column3Property.EncodedUtf8Bytes))
                {
                    result.Column3 = element.Value.GetInt32();
                }
                else
                {
                    // Capture unknown property in AdditionalData
                    if (result.AdditionalData is null)
                    {
                        result.AdditionalData = new Dictionary<string, object>();
                    }
                    result.AdditionalData.Add(element.Name, element.Value.Clone());
                }
            }
    
            return result;
        }
    
        public override void Write(Utf8JsonWriter writer,
            FieldValueSet value, JsonSerializerOptions options)
        {
            // To support roundtrip serialization:
            writer.WriteStartObject();
    
            writer.WriteString(ODataTypeProperty, value.ODataType);
            writer.WriteString(IdProperty, value.Id);
    
            if (value is FieldValueSetWithDataItem dataItem)
            {
                writer.WriteBoolean(ColumnProperty, dataItem.Column);
                writer.WriteString(Column2Property, dataItem.Column2);
                writer.WriteNumber(Column3Property, dataItem.Column3);
            }
    
            if (value.AdditionalData is not null)
            {
                foreach (var kvp in value.AdditionalData)
                {
                    writer.WritePropertyName(kvp.Key);
                    ((JsonElement)kvp.Value).WriteTo(writer);
                }
            }
            
            writer.WriteEndObject();
        }
    }
    

    最后,在发出请求时使用 JSON 转换器:

    // Use custom JSON converter when deserializing response
    var serializerOptions = new JsonSerializerOptions();
    serializerOptions.Converters.Add(new CustomFieldValueSetJsonConverter());
    
    var responseSerializer = new Serializer(serializerOptions);
    var responseHandler = new ResponseHandler(responseSerializer);
    
    var request = (ListItemsCollectionRequest)client.Sites[""].Lists[""].Items.Request();
    
    var listItems = await request
        .WithResponseHandler(responseHandler)
        .GetAsync();
    

    要访问您的列值:

    var col3 = ((FieldValueSetWithDataItem)listItem.Fields).Column3;
    

    【讨论】:

    • 似乎在某种程度上暴露了我已经拥有的往返行程中或多或少自动发生的事情? ListItems.Select(o =&gt; Serialize(o.Fields.AdditionalData)).Select(jsonO =&gt; Deserialize&lt;DataItem&gt;(jsonO)); 我也想知道是否有办法让 Graph API 省去分层元数据,只返回 JSON 数组中的字段。
    • 如果您有往返,您可以创建具有额外属性的 FieldSetValue 派生类型,而不是添加额外的字段,或者让转换器将反序列化的对象收集到列表中。
    • 我一定不明白“往返”是什么意思。从什么通过什么回到什么往返?请打开包装?
    • 列值作为FieldValueSet 类型的属性返回。 AdditionalData 是一个 JSON 序列化技巧,它捕获静态类型 FieldValueSet 未知的所有其他属性。由于您知道这些属性,因此您可以从 FieldValueSet 创建派生类并使用自定义 JSON 转换器创建实例,请参阅我的更新答案。
    • 简短回答是否定的,您不能将数组作为fields 的值返回。 MS Graph 文档明确指出,列是在 FieldValueSet 类型的对象中捕获的,因此它必须是 C# 中的对象。 AdditionalData 用于捕获自定义属性。我向您展示的方式是如何使用 Text.Json 自定义 JSON 序列化。如果您需要一个数组,您可以在 AdditionalData 中保存一个值数组(不是 JSON 元素)并使用 AdditionalData.Values,或者可以定义一个自定义类型以继承自 FieldValueSetIEnumerable
    【解决方案2】:

    您可能会发现 GraphServiceClient 的 HttpProvider 在这种情况下很有帮助:

            var listItemsCollectionRequest = graphServiceClient
             .Sites["<guid>"]
             .Lists["<nameOfList>"]
             .Items
             .Request()
             .Header("Accept", "application/json;odata.metadata=none")
             .Select("fields,id")
             .Expand("fields");
    
            using (var requestMessage = listItemsCollectionRequest.GetHttpRequestMessage())
            {
                using var responseMessage = await graphServiceClient.HttpProvider.SendAsync(requestMessage);
    
                //deserialize the response body into DataItem
            }
    

    通过使用 HttpProvider,您可以直接处理来自 Graph API 的响应并将响应主体反序列化到您的自定义类中。

    【讨论】:

      最近更新 更多