Skip to content

Commit

Permalink
Allow NewtonsoftJsonSerializer to be parameterized with settings.
Browse files Browse the repository at this point in the history
This allows custom settings such as turning off auto-magic date
parsing. (We need to turn this off for the BigQuery wrapper library.)
  • Loading branch information
jskeet committed May 4, 2017
1 parent 00c29ac commit c839319
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 31 deletions.
66 changes: 35 additions & 31 deletions Src/Support/GoogleApis.Core/Apis/Json/NewtonsoftJsonSerializer.cs
Expand Up @@ -32,22 +32,17 @@ namespace Google.Apis.Json
/// </summary>
public class RFC3339DateTimeConverter : JsonConverter
{
public override bool CanRead
{
get { return false; }
}
public override bool CanRead => false;

public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false.");
}

public override bool CanConvert(Type objectType)
{
public override bool CanConvert(Type objectType) =>
// Convert DateTime only.
return objectType == typeof(DateTime) || objectType == typeof(Nullable<DateTime>);
}
objectType == typeof(DateTime) || objectType == typeof(Nullable<DateTime>);

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Expand Down Expand Up @@ -83,36 +78,45 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
/// <summary>Class for serialization and deserialization of JSON documents using the Newtonsoft Library.</summary>
public class NewtonsoftJsonSerializer : IJsonSerializer
{
private static readonly JsonSerializerSettings newtonsoftSettings;
private static readonly JsonSerializer newtonsoftSerializer;
private readonly JsonSerializerSettings settings;
private readonly JsonSerializer serializer;

private static NewtonsoftJsonSerializer instance;
/// <summary>The default instance of the Newtonsoft JSON Serializer, with default settings.</summary>
public static NewtonsoftJsonSerializer Instance { get; } = new NewtonsoftJsonSerializer();

/// <summary>A singleton instance of the Newtonsoft JSON Serializer.</summary>
public static NewtonsoftJsonSerializer Instance
/// <summary>
/// Constructs a new instance with the default serialization settings, equivalent to <see cref="Instance"/>.
/// </summary>
public NewtonsoftJsonSerializer() : this(CreateDefaultSettings())
{
get
{
return (instance = instance ?? new NewtonsoftJsonSerializer());
}
}

static NewtonsoftJsonSerializer()
/// <summary>
/// Constructs a new instance with the given settings.
/// </summary>
/// <param name="settings">The settings to apply when serializing and deserializing. Must not be null.</param>
public NewtonsoftJsonSerializer(JsonSerializerSettings settings)
{
// Initialize the Newtonsoft serializer.
newtonsoftSettings = new JsonSerializerSettings
Utilities.ThrowIfNull(settings, nameof(settings));
this.settings = settings;
serializer = JsonSerializer.Create(settings);
}

/// <summary>
/// Creates a new instance of <see cref="JsonSerializerSettings"/> with the same behavior
/// as the ones used in <see cref="Instance"/>. This method is expected to be used to construct
/// settings which are then passed to <see cref="NewtonsoftJsonSerializer.NewtonsoftJsonSerializer(JsonSerializerSettings)"/>.
/// </summary>
/// <returns>A new set of default settings.</returns>
public static JsonSerializerSettings CreateDefaultSettings() =>
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
Converters = { new RFC3339DateTimeConverter(), new ExplicitNullConverter() }
};
newtonsoftSerializer = JsonSerializer.Create(newtonsoftSettings);
}

public string Format
{
get { return "json"; }
}
public string Format => "json";

public void Serialize(object obj, Stream target)
{
Expand All @@ -122,7 +126,7 @@ public void Serialize(object obj, Stream target)
{
obj = string.Empty;
}
newtonsoftSerializer.Serialize(writer, obj);
serializer.Serialize(writer, obj);
}
}

Expand All @@ -134,7 +138,7 @@ public string Serialize(object obj)
{
obj = string.Empty;
}
newtonsoftSerializer.Serialize(tw, obj);
serializer.Serialize(tw, obj);
return tw.ToString();
}
}
Expand All @@ -145,7 +149,7 @@ public T Deserialize<T>(string input)
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(input, newtonsoftSettings);
return JsonConvert.DeserializeObject<T>(input, settings);
}

public object Deserialize(string input, Type type)
Expand All @@ -154,15 +158,15 @@ public object Deserialize(string input, Type type)
{
return null;
}
return JsonConvert.DeserializeObject(input, type, newtonsoftSettings);
return JsonConvert.DeserializeObject(input, type, settings);
}

public T Deserialize<T>(Stream input)
{
// Convert the JSON document into an object.
using (StreamReader streamReader = new StreamReader(input))
{
return (T)newtonsoftSerializer.Deserialize(streamReader, typeof(T));
return (T)serializer.Deserialize(streamReader, typeof(T));
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Src/Support/GoogleApis.Tests/GoogleApis.Tests.csproj
Expand Up @@ -117,6 +117,7 @@
<Compile Include="Apis\Utils\StringValueAttributeTest.cs" />
<Compile Include="Apis\Utils\RepeatableTest.cs" />
<Compile Include="ApplicationContextTests.cs" />
<Compile Include="Json\NewtonsoftJsonSerializer.cs" />
<Compile Include="Json\JsonExplicitNullTest.cs" />
<Compile Include="Logging\Log4NetLogger.cs" />
<Compile Include="Program.cs" />
Expand Down
67 changes: 67 additions & 0 deletions Src/Support/GoogleApis.Tests/Json/NewtonsoftJsonSerializer.cs
@@ -0,0 +1,67 @@
/*
Copyright 2017 Google Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using Google.Apis.Json;
using Newtonsoft.Json;
using NUnit.Framework;
using System;

namespace Google.Apis.Tests.Json
{
class NewtonsoftJsonSerializerTest
{
[Test]
public void CreateDefaultSettings_Independent()
{
var settings1 = NewtonsoftJsonSerializer.CreateDefaultSettings();
var settings2 = NewtonsoftJsonSerializer.CreateDefaultSettings();

settings1.DateParseHandling = DateParseHandling.DateTimeOffset;
settings2.DateParseHandling = DateParseHandling.None;
// Not affected by changing settings2
Assert.AreEqual(DateParseHandling.DateTimeOffset, settings1.DateParseHandling);
}

[Test]
public void InstanceIsCached()
{
var instance1 = NewtonsoftJsonSerializer.Instance;
var instance2 = NewtonsoftJsonSerializer.Instance;
Assert.AreSame(instance1, instance2);
}

[Test]
public void DefaultInstanceParsesDates()
{
string text = "\"2017-05-03T16:38:00Z\"";
object value = NewtonsoftJsonSerializer.Instance.Deserialize<object>(text);
Assert.IsInstanceOf<DateTime>(value);
}

[Test]
public void CustomInstanceAvoidingDateParsin()
{
string text = "\"2017-05-03T16:38:00Z\"";
var settings = NewtonsoftJsonSerializer.CreateDefaultSettings();
settings.DateParseHandling = DateParseHandling.None;
var instance = new NewtonsoftJsonSerializer(settings);
object value = instance.Deserialize<object>(text);

// No magic parsing to DateTime...
Assert.IsInstanceOf<string>(value);
}
}
}

0 comments on commit c839319

Please sign in to comment.