diff --git a/Build/build.ps1 b/Build/build.ps1 index d688a1de..ad0fcedc 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -18,7 +18,7 @@ ) } -task default -depends Build +task default -depends Test task Clean { Set-Location $baseDir diff --git a/Build/runbuild.cmd b/Build/runbuild.cmd index fa2115ea..65c7d0c7 100644 --- a/Build/runbuild.cmd +++ b/Build/runbuild.cmd @@ -1 +1,2 @@ -powershell -Command "& {Import-Module ..\Tools\PSake\psake.psm1; Invoke-psake .\build.ps1 %*}" \ No newline at end of file +powershell -Command "& {Import-Module ..\Tools\PSake\psake.psm1; Invoke-psake .\build.ps1 %*}" +pause \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Converters/CustomCreationConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/CustomCreationConverterTests.cs index 3ed49756..f50f085a 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/CustomCreationConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/CustomCreationConverterTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Newtonsoft.Json.Converters; +using NUnit.Framework; namespace Newtonsoft.Json.Tests.Converters { @@ -87,5 +88,62 @@ namespace Newtonsoft.Json.Tests.Converters Console.WriteLine(employee.JobTitle); // Support } + + public class MyClass + { + public string Value { get; set; } + + [JsonConverter(typeof(MyThingConverter))] + public IThing Thing { get; set; } + } + + public interface IThing + { + int Number { get; } + } + + public class MyThing : IThing + { + public int Number { get; set; } + } + + public class MyThingConverter : CustomCreationConverter + { + public override IThing Create(Type objectType) + { + return new MyThing(); + } + } + + [Test] + public void AssertDoesDeserialize() + { + const string json = @"{ +""Value"": ""A value"", +""Thing"": { +""Number"": 123 +} +} +"; + MyClass myClass = JsonConvert.DeserializeObject(json); + Assert.IsNotNull(myClass); + Assert.AreEqual("A value", myClass.Value); + Assert.IsNotNull(myClass.Thing); + Assert.AreEqual(123, myClass.Thing.Number); + } + + [Test] + public void AssertShouldSerializeTest() + { + MyClass myClass = new MyClass + { + Value = "Foo", + Thing = new MyThing { Number = 456, } + }; + string json = JsonConvert.SerializeObject(myClass); // <-- Exception here + + const string expected = @"{""Value"":""Foo"",""Thing"":{""Number"":456}}"; + Assert.AreEqual(expected, json); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Converters/HtmlColorConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/HtmlColorConverterTests.cs new file mode 100644 index 00000000..8bafa43b --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Converters/HtmlColorConverterTests.cs @@ -0,0 +1,64 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +#if !SILVERLIGHT && !PocketPC +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using Newtonsoft.Json.Converters; +using NUnit.Framework; + +namespace Newtonsoft.Json.Tests.Converters +{ + public class HtmlColorConverterTests : TestFixtureBase + { + [Test] + public void WriteJsonTest() + { + string json; + + json = JsonConvert.SerializeObject(Color.DarkGray, new HtmlColorConverter()); + Assert.AreEqual(@"""DarkGray""", json); + + json = JsonConvert.SerializeObject(Color.FromArgb(255, 1, 2, 3), new HtmlColorConverter()); + Assert.AreEqual(@"""#010203""", json); + } + + [Test] + public void ReadJsonTest() + { + string json = @"""#010203"""; + + Color c = JsonConvert.DeserializeObject(json, new HtmlColorConverter()); + Assert.AreEqual(c.A, 255); + Assert.AreEqual(c.R, 1); + Assert.AreEqual(c.G, 2); + Assert.AreEqual(c.B, 3); + } + } +} +#endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj index a5e74013..7f7e63ab 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj @@ -62,8 +62,10 @@ + + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj index 2b3f1a55..7eefe6f8 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj @@ -57,6 +57,7 @@ + @@ -68,6 +69,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Silverlight.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Silverlight.csproj index 86ff7547..853046c2 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Silverlight.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Silverlight.csproj @@ -71,8 +71,10 @@ + + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index 9883da68..c811b985 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -63,6 +63,7 @@ 3.5 + 3.0 @@ -89,6 +90,7 @@ + True diff --git a/Src/Newtonsoft.Json/Converters/CustomCreationConverter.cs b/Src/Newtonsoft.Json/Converters/CustomCreationConverter.cs index d5f6441e..e20fbfc9 100644 --- a/Src/Newtonsoft.Json/Converters/CustomCreationConverter.cs +++ b/Src/Newtonsoft.Json/Converters/CustomCreationConverter.cs @@ -79,5 +79,16 @@ namespace Newtonsoft.Json.Converters { return typeof (T).IsAssignableFrom(objectType); } + + /// + /// Gets a value indicating whether this can write JSON. + /// + /// + /// true if this can write JSON; otherwise, false. + /// + public override bool CanWrite + { + get { return false; } + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/HtmlColorConverter.cs b/Src/Newtonsoft.Json/Converters/HtmlColorConverter.cs index fdde9c3b..5ae24f8b 100644 --- a/Src/Newtonsoft.Json/Converters/HtmlColorConverter.cs +++ b/Src/Newtonsoft.Json/Converters/HtmlColorConverter.cs @@ -68,6 +68,17 @@ namespace Newtonsoft.Json.Converters { throw new NotImplementedException(); } + + /// + /// Gets a value indicating whether this can read JSON. + /// + /// + /// true if this can read JSON; otherwise, false. + /// + public override bool CanRead + { + get { return false; } + } } } #endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonConverter.cs b/Src/Newtonsoft.Json/JsonConverter.cs index 27f356c4..493e2525 100644 --- a/Src/Newtonsoft.Json/JsonConverter.cs +++ b/Src/Newtonsoft.Json/JsonConverter.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; using System.Text; using Newtonsoft.Json.Utilities; +using Newtonsoft.Json.Schema; namespace Newtonsoft.Json { @@ -60,5 +61,32 @@ namespace Newtonsoft.Json /// true if this instance can convert the specified object type; otherwise, false. /// public abstract bool CanConvert(Type objectType); + + /// + /// Gets the of the JSON produced by the JsonConverter. + /// + /// The of the JSON produced by the JsonConverter. + public virtual JsonSchema GetSchema() + { + return null; + } + + /// + /// Gets a value indicating whether this can read JSON. + /// + /// true if this can read JSON; otherwise, false. + public virtual bool CanRead + { + get { return true; } + } + + /// + /// Gets a value indicating whether this can write JSON. + /// + /// true if this can write JSON; otherwise, false. + public virtual bool CanWrite + { + get { return true; } + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index f9435793..f759597c 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -432,18 +432,14 @@ namespace Newtonsoft.Json serializerWriter.Serialize(jsonWriter, value); } - internal bool HasMatchingConverter(Type type, out JsonConverter matchingConverter) + internal JsonConverter GetMatchingConverter(Type type) { - if (HasMatchingConverter(_converters, type, out matchingConverter)) - return true; - - return false; + return GetMatchingConverter(_converters, type); } - internal static bool HasMatchingConverter(IList converters, Type objectType, out JsonConverter matchingConverter) + internal static JsonConverter GetMatchingConverter(IList converters, Type objectType) { - if (objectType == null) - throw new ArgumentNullException("objectType"); + ValidationUtils.ArgumentNotNull(objectType, "objectType"); if (converters != null) { @@ -452,15 +448,11 @@ namespace Newtonsoft.Json JsonConverter converter = converters[i]; if (converter.CanConvert(objectType)) - { - matchingConverter = converter; - return true; - } + return converter; } } - matchingConverter = null; - return false; + return null; } internal void OnError(ErrorEventArgs e) diff --git a/Src/Newtonsoft.Json/Linq/JValue.cs b/Src/Newtonsoft.Json/Linq/JValue.cs index 3d954604..8df6dd95 100644 --- a/Src/Newtonsoft.Json/Linq/JValue.cs +++ b/Src/Newtonsoft.Json/Linq/JValue.cs @@ -273,7 +273,7 @@ namespace Newtonsoft.Json.Linq { JsonConverter matchingConverter; - if (value != null && JsonSerializer.HasMatchingConverter(converters, value.GetType(), out matchingConverter)) + if (value != null && ((matchingConverter = JsonSerializer.GetMatchingConverter(converters, value.GetType())) != null)) matchingConverter.WriteJson(writer, value, new JsonSerializer()); else defaultWrite(value); diff --git a/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs b/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs index a78c81e9..805bccfb 100644 --- a/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs +++ b/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs @@ -234,6 +234,15 @@ namespace Newtonsoft.Json.Schema throw new Exception("Unresolved circular reference for type '{0}'. Explicitly define an Id for the type using a JsonObject/JsonArray attribute or automatically generate a type Id using the UndefinedSchemaIdHandling property.".FormatWith(CultureInfo.InvariantCulture, type)); } + JsonContract contract = ContractResolver.ResolveContract(type); + JsonConverter converter; + if ((converter = contract.Converter) != null || (converter = contract.InternalConverter) != null) + { + JsonSchema converterSchema = converter.GetSchema(); + if (converterSchema != null) + return converterSchema; + } + Push(new TypeSchema(type, new JsonSchema())); if (explicitId != null) @@ -244,8 +253,7 @@ namespace Newtonsoft.Json.Schema CurrentSchema.Title = GetTitle(type); CurrentSchema.Description = GetDescription(type); - JsonContract contract = ContractResolver.ResolveContract(type); - if (contract.Converter != null || contract.InternalConverter != null) + if (converter != null) { // todo: Add GetSchema to JsonConverter and use here? CurrentSchema.Type = JsonSchemaType.Any; diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index 73eb3bb4..62d10f41 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -204,9 +204,7 @@ namespace Newtonsoft.Json.Serialization contract.Converter = ResolveContractConverter(contract.UnderlyingType); // then see whether object is compadible with any of the built in converters - JsonConverter internalConverter; - if (JsonSerializer.HasMatchingConverter(BuiltInConverters, contract.UnderlyingType, out internalConverter)) - contract.InternalConverter = internalConverter; + contract.InternalConverter = JsonSerializer.GetMatchingConverter(BuiltInConverters, contract.UnderlyingType); if (ReflectionUtils.HasDefaultConstructor(contract.CreatedType, true) || contract.CreatedType.IsValueType) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index f456fbb9..d5e832a3 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -179,24 +179,29 @@ namespace Newtonsoft.Json.Serialization private object CreateValue(JsonReader reader, Type objectType, JsonContract contract, object existingValue, JsonConverter memberConverter) { - JsonConverter converter; - - // member attribute converter + JsonConverter converter = null; if (memberConverter != null) - return memberConverter.ReadJson(reader, objectType, GetInternalSerializer()); + { + // member attribute converter + converter = memberConverter; + } + else if (contract != null) + { + JsonConverter matchingConverter; + if (contract.Converter != null) + // class attribute converter + converter = contract.Converter; + else if ((matchingConverter = Serializer.GetMatchingConverter(contract.UnderlyingType)) != null) + // passed in converters + converter = matchingConverter; + else if (contract.InternalConverter != null) + // internally specified converter + converter = contract.InternalConverter; + } - // class attribute converter - if (contract != null && contract.Converter != null) - return contract.Converter.ReadJson(reader, objectType, GetInternalSerializer()); - - // passed in converters - if (objectType != null && Serializer.HasMatchingConverter(objectType, out converter)) + if (converter != null && converter.CanRead) return converter.ReadJson(reader, objectType, GetInternalSerializer()); - // internally specified converter - if (contract != null && contract.InternalConverter != null) - return contract.InternalConverter.ReadJson(reader, objectType, GetInternalSerializer()); - if (contract is JsonLinqContract) return CreateJToken(reader, contract); diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index 59d0517c..c01b72a3 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -90,10 +90,11 @@ namespace Newtonsoft.Json.Serialization return; } - if (converter != null + if ((converter != null || ((converter = contract.Converter) != null) - || Serializer.HasMatchingConverter(contract.UnderlyingType, out converter) + || ((converter = Serializer.GetMatchingConverter(contract.UnderlyingType)) != null) || ((converter = contract.InternalConverter) != null)) + && converter.CanWrite) { SerializeConvertable(writer, converter, value, contract); }