Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization.Converters;
using System.Text.Json.Serialization.Metadata;

Expand Down Expand Up @@ -458,8 +460,27 @@ internal bool TryWriteDataExtensionProperty(Utf8JsonWriter writer, T value, Json
{
// If not JsonDictionaryConverter<T> then we are JsonObject.
// Avoid a type reference to JsonObject and its converter to support trimming.
Debug.Assert(Type == typeof(Nodes.JsonObject));
return TryWrite(writer, value, options, ref state);
Debug.Assert(Type == typeof(JsonObject));

// Write the JsonObject extension data contents without the wrapping braces.
// This is necessary because extension data properties should be flattened
// into the parent object, not nested as a separate object.
JsonObject jsonObject = (JsonObject)(object)value!;
foreach (KeyValuePair<string, JsonNode?> entry in jsonObject)
{
writer.WritePropertyName(entry.Key);

if (entry.Value is null)
{
writer.WriteNullValue();
}
else
{
entry.Value.WriteTo(writer, options);
}
}

return true;
}

if (writer.CurrentDepth >= options.EffectiveMaxDepth)
Expand Down
94 changes: 94 additions & 0 deletions src/libraries/System.Text.Json/tests/Common/ExtensionDataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,100 @@ public async Task DeserializeIntoJsonObjectProperty()
Assert.Equal(1, obj.MyOverflow["MyDict"]["Property1"].GetValue<int>());
}

[Fact]
public async Task SerializeJsonObjectExtensionData_ProducesValidJson()
{
// JsonSerializer.Serialize was producing invalid JSON for [JsonExtensionData] property of type JsonObject
// Output was: {"Id":1,{"nested":true}} instead of {"Id":1,"nested":true}

var obj = new ClassWithJsonObjectExtensionDataAndProperty
{
Id = 1,
Extra = new JsonObject { ["nested"] = true }
};

string json = await Serializer.SerializeWrapper(obj);

// Verify the JSON is valid by parsing it
using JsonDocument doc = JsonDocument.Parse(json);

// Verify the structure is correct: extension data should be flattened, not nested
Assert.True(doc.RootElement.TryGetProperty("Id", out JsonElement idElement));
Assert.Equal(1, idElement.GetInt32());

Assert.True(doc.RootElement.TryGetProperty("nested", out JsonElement nestedElement));
Assert.True(nestedElement.GetBoolean());

// Extension data property name should NOT appear in the output
Assert.False(doc.RootElement.TryGetProperty("Extra", out _));

// Verify expected JSON format
Assert.Equal(@"{""Id"":1,""nested"":true}", json);
}

[Fact]
public async Task SerializeJsonObjectExtensionData_RoundTrip()
{
var original = new ClassWithJsonObjectExtensionDataAndProperty
{
Id = 42,
Extra = new JsonObject
{
["string"] = "value",
["number"] = 123,
["boolean"] = false,
["nested"] = new JsonObject { ["inner"] = "data" }
}
};

string json = await Serializer.SerializeWrapper(original);

// Verify round-trip
var deserialized = await Serializer.DeserializeWrapper<ClassWithJsonObjectExtensionDataAndProperty>(json);

Assert.Equal(original.Id, deserialized.Id);
Assert.NotNull(deserialized.Extra);
Assert.Equal(4, deserialized.Extra.Count);
Assert.Equal("value", deserialized.Extra["string"]!.GetValue<string>());
Assert.Equal(123, deserialized.Extra["number"]!.GetValue<int>());
Assert.False(deserialized.Extra["boolean"]!.GetValue<bool>());
Assert.Equal("data", deserialized.Extra["nested"]!["inner"]!.GetValue<string>());
}

[Fact]
public async Task SerializeJsonObjectExtensionData_EmptyJsonObject()
{
var obj = new ClassWithJsonObjectExtensionDataAndProperty
{
Id = 1,
Extra = new JsonObject()
};

string json = await Serializer.SerializeWrapper(obj);
Assert.Equal(@"{""Id"":1}", json);
}

[Fact]
public async Task SerializeJsonObjectExtensionData_NullJsonObject()
{
var obj = new ClassWithJsonObjectExtensionDataAndProperty
{
Id = 1,
Extra = null
};

string json = await Serializer.SerializeWrapper(obj);
Assert.Equal(@"{""Id"":1}", json);
}

public class ClassWithJsonObjectExtensionDataAndProperty
{
public int Id { get; set; }

[JsonExtensionData]
public JsonObject? Extra { get; set; }
}

[Fact]
#if BUILDING_SOURCE_GENERATOR_TESTS
[ActiveIssue("https://github.com/dotnet/runtime/issues/58945")]
Expand Down
Loading