Skip to content
This repository has been archived by the owner on Jul 9, 2024. It is now read-only.

Support untyped nodes #198

Merged
merged 2 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.2.0] - 2024-03-22

### Added

- Added support for untyped nodes. (https://github.com/microsoft/kiota-serialization-json-dotnet/issues/197)

## [1.1.8] - 2024-02-27

- Reduced `DynamicallyAccessedMembers` scope for enum methods to prevent ILC warnings.
Expand Down
79 changes: 79 additions & 0 deletions Microsoft.Kiota.Serialization.Json.Tests/JsonParseNodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using System.Text.Json;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Serialization.Json.Tests.Converters;
using Microsoft.Kiota.Serialization.Json.Tests.Mocks;
using Xunit;
Expand Down Expand Up @@ -63,6 +64,46 @@ public class JsonParseNodeTests
" \"id\": \"48d31887-5fad-4d73-a9f5-3c356e68a038\"\r\n" +
"}";

private const string TestUntypedJson = "{\r\n" +
" \"@odata.context\": \"https://graph.microsoft.com/v1.0/$metadata#sites('contoso.sharepoint.com')/lists('fa631c4d-ac9f-4884-a7f5-13c659d177e3')/items('1')/fields/$entity\",\r\n" +
" \"id\": \"5\",\r\n" +
" \"title\": \"Project 101\",\r\n" +
" \"location\": {\r\n" +
" \"address\": {\r\n" +
" \"city\": \"Redmond\",\r\n" +
" \"postalCode\": \"98052\",\r\n" +
" \"state\": \"Washington\",\r\n" +
" \"street\": \"NE 36th St\"\r\n" +
" },\r\n" +
" \"coordinates\": {\r\n" +
" \"latitude\": 47.641942,\r\n" +
" \"longitude\": -122.127222\r\n" +
" },\r\n" +
" \"displayName\": \"Microsoft Building 92\",\r\n" +
" \"floorCount\": 50,\r\n" +
" \"hasReception\": true,\r\n" +
" \"contact\": null\r\n" +
" },\r\n" +
" \"keywords\": [\r\n" +
" {\r\n" +
" \"created\": \"2023-07-26T10:41:26Z\",\r\n" +
" \"label\": \"Keyword1\",\r\n" +
" \"termGuid\": \"10e9cc83-b5a4-4c8d-8dab-4ada1252dd70\",\r\n" +
" \"wssId\": 6442450942\r\n" +
" },\r\n" +
" {\r\n" +
" \"created\": \"2023-07-26T10:51:26Z\",\r\n" +
" \"label\": \"Keyword2\",\r\n" +
" \"termGuid\": \"2cae6c6a-9bb8-4a78-afff-81b88e735fef\",\r\n" +
" \"wssId\": 6442450943\r\n" +
" }\r\n" +
" ],\r\n" +
" \"detail\": null,\r\n" +
" \"extra\": {\r\n" +
" \"createdDateTime\":\"2024-01-15T00:00:00\\u002B00:00\"\r\n" +
" }\r\n" +
"}";

private static readonly string TestUserCollectionString = $"[{TestUserJson}]";

[Fact]
Expand Down Expand Up @@ -178,5 +219,43 @@ public void ParseGuidWithoutConverter()
// Assert
Assert.Equal(id, entity.Id);
}

[Fact]
public void GetEntityWithUntypedNodesFromJson()
{
// Arrange
using var jsonDocument = JsonDocument.Parse(TestUntypedJson);
var rootParseNode = new JsonParseNode(jsonDocument.RootElement);
// Act
var entity = rootParseNode.GetObjectValue(UntypedTestEntity.CreateFromDiscriminatorValue);
// Assert
Assert.NotNull(entity);
Assert.Equal("5", entity.Id);
Assert.Equal("Project 101", entity.Title);
Assert.NotNull(entity.Location);
Assert.IsType<UntypedObject>(entity.Location); // creates untyped object
var location = (UntypedObject)entity.Location;
var locationProperties = location.GetValue();
Assert.IsType<UntypedObject>(locationProperties["address"]);
Assert.IsType<UntypedString>(locationProperties["displayName"]); // creates untyped string
Assert.IsType<UntypedInteger>(locationProperties["floorCount"]); // creates untyped number
Assert.IsType<UntypedBoolean>(locationProperties["hasReception"]); // creates untyped boolean
Assert.IsType<UntypedNull>(locationProperties["contact"]); // creates untyped null
Assert.IsType<UntypedObject>(locationProperties["coordinates"]); // creates untyped null
var coordinates = (UntypedObject)locationProperties["coordinates"];
var coordinatesProperties = coordinates.GetValue();
Assert.IsType<UntypedDecimal>(coordinatesProperties["latitude"]); // creates untyped decimal
Assert.IsType<UntypedDecimal>(coordinatesProperties["longitude"]);
Assert.Equal("Microsoft Building 92", ((UntypedString)locationProperties["displayName"]).GetValue());
Assert.Equal(50, ((UntypedInteger)locationProperties["floorCount"]).GetValue());
Assert.True(((UntypedBoolean)locationProperties["hasReception"]).GetValue());
Assert.Null(((UntypedNull)locationProperties["contact"]).GetValue());
Assert.NotNull(entity.Keywords);
Assert.IsType<UntypedArray>(entity.Keywords); // creates untyped array
Assert.Equal(2, ((UntypedArray)entity.Keywords).GetValue().Count());
Assert.Null(entity.Detail);
var extra = entity.AdditionalData["extra"];
Assert.NotNull(extra);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Text;
using System.Text.Json;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Serialization.Json.Tests.Converters;
using Microsoft.Kiota.Serialization.Json.Tests.Mocks;
using Xunit;
Expand Down Expand Up @@ -245,5 +246,82 @@ public void WriteGuidUsingNoConverter()
var expectedString = $"{{\"id\":\"{id:D}\"}}";
Assert.Equal(expectedString, serializedJsonString);
}
[Fact]
public void WritesSampleObjectValueWithUntypedProperties()
{
// Arrange
var untypedTestEntity = new UntypedTestEntity
{
Id = "1",
Title = "Title",
Location = new UntypedObject(new Dictionary<string, UntypedNode>
{
{"address", new UntypedObject(new Dictionary<string, UntypedNode>
{
{"city", new UntypedString("Redmond") },
{"postalCode", new UntypedString("98052") },
{"state", new UntypedString("Washington") },
{"street", new UntypedString("NE 36th St") }
})},
{"coordinates", new UntypedObject(new Dictionary<string, UntypedNode>
{
{"latitude", new UntypedDouble(47.641942d) },
{"longitude", new UntypedDouble(-122.127222d) }
})},
{"displayName", new UntypedString("Microsoft Building 92") },
{"floorCount", new UntypedInteger(50) },
{"hasReception", new UntypedBoolean(true) },
{"contact", new UntypedNull() }
}),
Keywords = new UntypedArray(new List<UntypedNode>
{
new UntypedObject(new Dictionary<string, UntypedNode>
{
{"created", new UntypedString("2023-07-26T10:41:26Z") },
{"label", new UntypedString("Keyword1") },
{"termGuid", new UntypedString("10e9cc83-b5a4-4c8d-8dab-4ada1252dd70") },
{"wssId", new UntypedLong(6442450941) }
}),
new UntypedObject(new Dictionary<string, UntypedNode>
{
{"created", new UntypedString("2023-07-26T10:51:26Z") },
{"label", new UntypedString("Keyword2") },
{"termGuid", new UntypedString("2cae6c6a-9bb8-4a78-afff-81b88e735fef") },
{"wssId", new UntypedLong(6442450942) }
})
}),
AdditionalData = new Dictionary<string, object>
{
{ "extra", new UntypedObject(new Dictionary<string, UntypedNode>
{
{"createdDateTime", new UntypedString("2024-01-15T00:00:00+00:00") }
}) }
}
};
using var jsonSerializerWriter = new JsonSerializationWriter();
// Act
jsonSerializerWriter.WriteObjectValue(string.Empty, untypedTestEntity);
// Get the json string from the stream.
var serializedStream = jsonSerializerWriter.GetSerializedContent();
using var reader = new StreamReader(serializedStream, Encoding.UTF8);
var serializedJsonString = reader.ReadToEnd();

// Assert
var expectedString = "{" +
"\"id\":\"1\"," +
"\"title\":\"Title\"," +
"\"location\":{" +
"\"address\":{\"city\":\"Redmond\",\"postalCode\":\"98052\",\"state\":\"Washington\",\"street\":\"NE 36th St\"}," +
"\"coordinates\":{\"latitude\":47.641942,\"longitude\":-122.127222}," +
"\"displayName\":\"Microsoft Building 92\"," +
"\"floorCount\":50," +
"\"hasReception\":true," +
"\"contact\":null}," +
"\"keywords\":[" +
"{\"created\":\"2023-07-26T10:41:26Z\",\"label\":\"Keyword1\",\"termGuid\":\"10e9cc83-b5a4-4c8d-8dab-4ada1252dd70\",\"wssId\":6442450941}," +
"{\"created\":\"2023-07-26T10:51:26Z\",\"label\":\"Keyword2\",\"termGuid\":\"2cae6c6a-9bb8-4a78-afff-81b88e735fef\",\"wssId\":6442450942}]," +
"\"extra\":{\"createdDateTime\":\"2024-01-15T00:00:00\\u002B00:00\"}}";
Assert.Equal(expectedString, serializedJsonString);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

using System;
using System.Collections.Generic;
using Microsoft.Kiota.Abstractions.Serialization;

namespace Microsoft.Kiota.Serialization.Json.Tests.Mocks;

public class UntypedTestEntity : IParsable, IAdditionalDataHolder
{
/// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary>
public IDictionary<string, object> AdditionalData { get; set; }
public string Id { get; set; }
public string Title { get; set; }
public UntypedNode Location { get; set; }
public UntypedNode Keywords { get; set; }
public UntypedNode Detail { get; set; }
public UntypedTestEntity()
{
AdditionalData = new Dictionary<string, object>();
}

public IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
return new Dictionary<string, Action<IParseNode>>
{
{ "id", node => Id = node.GetStringValue() },
{ "title", node => Title = node.GetStringValue() },
{ "location", node => Location = node.GetObjectValue(UntypedNode.CreateFromDiscriminatorValue) },
{ "keywords", node => Keywords = node.GetObjectValue(UntypedNode.CreateFromDiscriminatorValue) },
{ "detail", node => Detail = node.GetObjectValue(UntypedNode.CreateFromDiscriminatorValue) }
};
}
public void Serialize(ISerializationWriter writer)
{
writer.WriteStringValue("id", Id);
writer.WriteStringValue("title", Title);
writer.WriteObjectValue("location", Location);
writer.WriteObjectValue("keywords", Keywords);
writer.WriteObjectValue("detail", Detail);
writer.WriteAdditionalData(AdditionalData);
}
public static UntypedTestEntity CreateFromDiscriminatorValue(IParseNode parseNode)
{
var discriminatorValue = parseNode.GetChildNode("@odata.type")?.GetStringValue();
return discriminatorValue switch
{
_ => new UntypedTestEntity(),
};
}
}
Loading
Loading