Skip to content

Commit

Permalink
Clean up DuplicatePropertyNameHandling feature
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK committed Nov 24, 2018
1 parent a1f1b0a commit 88dae52
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1770,11 +1770,11 @@ public async Task ThrowOnDuplicateKeysDeserializingAsync()
}
";

JsonLoadSettings settings = new JsonLoadSettings { DuplicatePropertyNamesHandling = DuplicatePropertyNamesHandling.Throw };
JsonLoadSettings settings = new JsonLoadSettings { DuplicatePropertyNameHandling = DuplicatePropertyNameHandling.Error };

JsonTextReader reader = new JsonTextReader(new StringReader(json));
await ExceptionAssert.ThrowsAsync<JsonException>( async () => await JToken.ReadFromAsync(reader, settings));
await ExceptionAssert.ThrowsAsync<JsonReaderException>(async () => await JToken.ReadFromAsync(reader, settings));
}
}

}
#endif
5 changes: 2 additions & 3 deletions Src/Newtonsoft.Json.Tests/JsonTextReaderTests/ReadTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1761,12 +1761,11 @@ public void ThrowOnDuplicateKeysDeserializing()
}
";

JsonLoadSettings settings = new JsonLoadSettings {DuplicatePropertyNamesHandling = DuplicatePropertyNamesHandling.Throw};
JsonLoadSettings settings = new JsonLoadSettings {DuplicatePropertyNameHandling = DuplicatePropertyNameHandling.Error};

ExceptionAssert.Throws<JsonException>(() => JObject.Parse(json, settings));
JsonTextReader reader = new JsonTextReader(new StringReader(json));
ExceptionAssert.Throws<JsonException>(() =>
{
JsonTextReader reader = new JsonTextReader(new StringReader(json));
JToken.ReadFrom(reader, settings);
});
}
Expand Down
33 changes: 33 additions & 0 deletions Src/Newtonsoft.Json.Tests/Linq/JObjectAsyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,39 @@ await ExceptionAssert.ThrowsAsync<JsonReaderException>(async () =>
await JToken.ReadFromAsync(reader);
}, "Unexpected end of content while loading JObject. Path 'short.error.code', line 6, position 14.");
}

[Test]
public async Task ParseMultipleProperties_EmptySettingsAsync()
{
string json = @"{
""Name"": ""Name1"",
""Name"": ""Name2""
}";

JsonTextReader reader = new JsonTextReader(new StringReader(json));
JObject o = (JObject)await JToken.ReadFromAsync(reader, new JsonLoadSettings());
string value = (string)o["Name"];

Assert.AreEqual("Name2", value);
}

[Test]
public async Task ParseMultipleProperties_IgnoreDuplicateSettingAsync()
{
string json = @"{
""Name"": ""Name1"",
""Name"": ""Name2""
}";

JsonTextReader reader = new JsonTextReader(new StringReader(json));
JObject o = (JObject)await JToken.ReadFromAsync(reader, new JsonLoadSettings
{
DuplicatePropertyNameHandling = DuplicatePropertyNameHandling.Ignore
});
string value = (string)o["Name"];

Assert.AreEqual("Name1", value);
}
}
}

Expand Down
31 changes: 31 additions & 0 deletions Src/Newtonsoft.Json.Tests/Linq/JObjectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1604,6 +1604,37 @@ public void ParseMultipleProperties()
Assert.AreEqual("Name2", value);
}

[Test]
public void ParseMultipleProperties_EmptySettings()
{
string json = @"{
""Name"": ""Name1"",
""Name"": ""Name2""
}";

JObject o = JObject.Parse(json, new JsonLoadSettings());
string value = (string)o["Name"];

Assert.AreEqual("Name2", value);
}

[Test]
public void ParseMultipleProperties_IgnoreDuplicateSetting()
{
string json = @"{
""Name"": ""Name1"",
""Name"": ""Name2""
}";

JObject o = JObject.Parse(json, new JsonLoadSettings
{
DuplicatePropertyNameHandling = DuplicatePropertyNameHandling.Ignore
});
string value = (string)o["Name"];

Assert.AreEqual("Name1", value);
}

#if !(PORTABLE || DNXCORE50 || PORTABLE40) || NETSTANDARD2_0
[Test]
public void WriteObjectNullDBNullValue()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,19 @@ namespace Newtonsoft.Json.Linq
/// <summary>
/// Specifies how duplicate property names are handled when loading JSON.
/// </summary>
public enum DuplicatePropertyNamesHandling
public enum DuplicatePropertyNameHandling
{
/// <summary>
/// Ignore duplicate properties, use only last value (default)
/// Replace the existing value when there is a duplicate property. The value of the last property in the JSON object will be used.
/// </summary>
Ignore = 0,
Replace = 0,
/// <summary>
/// Throw exception
/// Ignore the new value when there is a duplicate property. The value of the first property in the JSON object will be used.
/// </summary>
Throw = 1
Ignore = 1,
/// <summary>
/// Throw a <see cref="JsonReaderException"/> when a duplicate property is encountered.
/// </summary>
Error = 2
}
}
18 changes: 4 additions & 14 deletions Src/Newtonsoft.Json/Linq/JContainer.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,25 +148,15 @@ private async Task ReadContentFromAsync(JsonReader reader, JsonLoadSettings sett
parent.Add(v);
break;
case JsonToken.PropertyName:
string propertyName = reader.Value.ToString();
JProperty property = new JProperty(propertyName);
property.SetLineInfo(lineInfo, settings);
JObject parentObject = (JObject)parent;
// handle multiple properties with the same name in JSON
JProperty existingPropertyWithName = parentObject.Property(propertyName);
if (existingPropertyWithName == null)
JProperty property = ReadProperty(reader, settings, lineInfo, parent);
if (property != null)
{
parent.Add(property);
parent = property;
}
else
{
if (settings != null && settings.DuplicatePropertyNamesHandling == DuplicatePropertyNamesHandling.Throw)
{
throw JsonException.Create(lineInfo, property.Path, $"Property with the same name ('{propertyName}') already exists");
}
existingPropertyWithName.Replace(property);
await reader.SkipAsync();
}
parent = property;
break;
default:
throw new InvalidOperationException("The JsonReader should not be on a token of type {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
Expand Down
52 changes: 38 additions & 14 deletions Src/Newtonsoft.Json/Linq/JContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -837,32 +837,56 @@ internal void ReadContentFrom(JsonReader r, JsonLoadSettings settings)
parent.Add(v);
break;
case JsonToken.PropertyName:
string propertyName = r.Value.ToString();
JProperty property = new JProperty(propertyName);
property.SetLineInfo(lineInfo, settings);
JObject parentObject = (JObject)parent;
// handle multiple properties with the same name in JSON
JProperty existingPropertyWithName = parentObject.Property(propertyName);
if (existingPropertyWithName == null)
JProperty property = ReadProperty(r, settings, lineInfo, parent);
if (property != null)
{
parent.Add(property);
parent = property;
}
else
{
if (settings != null && settings.DuplicatePropertyNamesHandling == DuplicatePropertyNamesHandling.Throw)
{
throw JsonException.Create(lineInfo, property.Path, $"Property with the same name ('{propertyName}') already exists");
}
existingPropertyWithName.Replace(property);
r.Skip();
}
parent = property;
break;
default:
throw new InvalidOperationException("The JsonReader should not be on a token of type {0}.".FormatWith(CultureInfo.InvariantCulture, r.TokenType));
}
} while (r.Read());
}

private static JProperty ReadProperty(JsonReader r, JsonLoadSettings settings, IJsonLineInfo lineInfo, JContainer parent)
{
DuplicatePropertyNameHandling duplicatePropertyNameHandling = settings?.DuplicatePropertyNameHandling ?? DuplicatePropertyNameHandling.Replace;

JObject parentObject = (JObject)parent;
string propertyName = r.Value.ToString();
JProperty existingPropertyWithName = parentObject.Property(propertyName);
if (existingPropertyWithName != null)
{
if (duplicatePropertyNameHandling == DuplicatePropertyNameHandling.Ignore)
{
return null;
}
else if (duplicatePropertyNameHandling == DuplicatePropertyNameHandling.Error)
{
throw JsonReaderException.Create(r, "Property with the name '{0}' already exists in the current JSON object.".FormatWith(CultureInfo.InvariantCulture, propertyName));
}
}

JProperty property = new JProperty(propertyName);
property.SetLineInfo(lineInfo, settings);
// handle multiple properties with the same name in JSON
if (existingPropertyWithName == null)
{
parent.Add(property);
}
else
{
existingPropertyWithName.Replace(property);
}

return property;
}

internal int ContentsHashCode()
{
int hashCode = 0;
Expand Down
19 changes: 11 additions & 8 deletions Src/Newtonsoft.Json/Linq/JsonLoadSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class JsonLoadSettings
{
private CommentHandling _commentHandling;
private LineInfoHandling _lineInfoHandling;
private DuplicatePropertyNamesHandling _duplicatePropertyNamesHandling;
private DuplicatePropertyNameHandling _duplicatePropertyNameHandling;

/// <summary>
/// Initializes a new instance of the <see cref="JsonLoadSettings"/> class.
Expand All @@ -18,11 +18,12 @@ public JsonLoadSettings()
{
_lineInfoHandling = LineInfoHandling.Load;
_commentHandling = CommentHandling.Ignore;
_duplicatePropertyNamesHandling = DuplicatePropertyNamesHandling.Ignore;
_duplicatePropertyNameHandling = DuplicatePropertyNameHandling.Replace;
}

/// <summary>
/// Gets or sets how JSON comments are handled when loading JSON.
/// The default value is <see cref="CommentHandling.Ignore" />.
/// </summary>
/// <value>The JSON comment handling.</value>
public CommentHandling CommentHandling
Expand All @@ -41,6 +42,7 @@ public CommentHandling CommentHandling

/// <summary>
/// Gets or sets how JSON line info is handled when loading JSON.
/// The default value is <see cref="LineInfoHandling.Load" />.
/// </summary>
/// <value>The JSON line info handling.</value>
public LineInfoHandling LineInfoHandling
Expand All @@ -58,20 +60,21 @@ public LineInfoHandling LineInfoHandling
}

/// <summary>
/// Gets or sets how duplicate property names are handled when loading JSON.
/// Gets or sets how duplicate property names in JSON objects are handled when loading JSON.
/// The default value is <see cref="DuplicatePropertyNameHandling.Replace" />.
/// </summary>
/// <value>The JSON duplicate property names handling.</value>
public DuplicatePropertyNamesHandling DuplicatePropertyNamesHandling
/// <value>The JSON duplicate property name handling.</value>
public DuplicatePropertyNameHandling DuplicatePropertyNameHandling
{
get => this._duplicatePropertyNamesHandling;
get => _duplicatePropertyNameHandling;
set
{
if (value< DuplicatePropertyNamesHandling.Ignore|| value> DuplicatePropertyNamesHandling.Throw)
if (value < DuplicatePropertyNameHandling.Ignore || value > DuplicatePropertyNameHandling.Error)
{
throw new ArgumentOutOfRangeException(nameof(value));
}

this._duplicatePropertyNamesHandling = value;
_duplicatePropertyNameHandling = value;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json/Newtonsoft.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<None Include="..\..\LICENSE.md" Pack="true" PackagePath="LICENSE.md" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta-62925-02" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta-63127-02" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net45'">
<AssemblyTitle>Json.NET</AssemblyTitle>
Expand Down
1 change: 0 additions & 1 deletion Src/global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"projects": [ "src" ],
"sdk": {
"version": "2.1.500"
}
Expand Down

0 comments on commit 88dae52

Please sign in to comment.