简体   繁体   中英

Transforming Microsoft Graph ListItem Output to a corresponding C# type

The work of transforming JSON data into a typed data model through seems to be made much more complex by the "help" the combination of SharePoint and MS Graph offer. :-)

I have a SharePoint List in Microsoft 365 that I'm accessing through the Graph API in C#, where the query destination is a typed class with properties identical to the SharePoint List Column Properties.

The ListItem class Graph API returns the results in the a Fields.AdditionalData of type Dictionary<string,object{System.Text.Json.JsonElement}> It needs to become an IEnumerable<DataItem> , which I can do by taking the List from the query result through a Serialize/Deserialize round trip, as below:

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));

Is there a way to do this, either with smarter OData or something in Graph API I haven't seen, without taking what used to be JSON and sending it back through JSON Serializers twice?

More details below: Sample output JSON from Graph Explorer, where value contains an array of :

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

Corresponding C# Class (literally built using "Paste JSON as class"):

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

The "Helper" classes in the C# Graph API deliver mostly transformed into the array of fields I actually need:

        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;

If you don't mind roundtrip serialization, you could customize the client's JSON serialization :

// 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();

Implement a custom 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 FieldValueSet();
        using var doc = JsonDocument.ParseValue(ref reader);
        var root = doc.RootElement;

        foreach (var element in root.EnumerateObject())
        {
            // Set OData type property
            if (element.NameEquals(ODataTypeProperty.EncodedUtf8Bytes))
            {
                result.ODataType = element.Value.GetString();
            }
            // Set Id property
            else if (element.NameEquals(IdProperty.EncodedUtf8Bytes))
            {
                result.Id = element.Value.GetString();
            }
            // Create DataItem in AdditionalData
            else if (element.NameEquals(ColumnProperty.EncodedUtf8Bytes)
                || element.NameEquals(Column2Property.EncodedUtf8Bytes)
                || element.NameEquals(Column3Property.EncodedUtf8Bytes))
            {
                result.AdditionalData ??= new Dictionary<string, object>();
                if (!result.AdditionalData.ContainsKey("DataItem"))
                {
                    result.AdditionalData.Add("DataItem", new DataItem());
                }

                var dataItem = (DataItem)result.AdditionalData["DataItem"];
                if (element.NameEquals(ColumnProperty.EncodedUtf8Bytes))
                {
                    dataItem.Column = element.Value.GetBoolean();
                }
                else if (element.NameEquals(ColumnProperty.EncodedUtf8Bytes))
                {
                    dataItem.Column2 = element.Value.GetString();
                }
                else if (element.NameEquals(ColumnProperty.EncodedUtf8Bytes))
                {
                    dataItem.Column3 = element.Value.GetInt32();
                }
            }
        }

        return result;
    }

    public override void Write(Utf8JsonWriter writer, 
        FieldValueSet value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

To access the DataItem object:

var dataItem = (DataItem)listItem.Fields.AdditionalData["DataItem"];

You may find the HttpProvider of the GraphServiceClient helpful in this scenario:

        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
        }

By using the HttpProvider you can directly work with the response from the Graph API and deserialize the response body into your custom class.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM