diff --git a/NuGet.config b/NuGet.config deleted file mode 100644 index 9ce5681d..00000000 --- a/NuGet.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Schema.NET.sln b/Schema.NET.sln index 48d4fcb2..cbda0f49 100644 --- a/Schema.NET.sln +++ b/Schema.NET.sln @@ -15,7 +15,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution build.cake = build.cake Key.snk = Key.snk MinimumRecommendedRulesWithStyleCop.ruleset = MinimumRecommendedRulesWithStyleCop.ruleset - NuGet.config = NuGet.config EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schema.NET.Tool", "Source\Schema.NET.Tool\Schema.NET.Tool.csproj", "{3CED3D1A-AB36-4B39-86DC-910BF8237DE9}" diff --git a/Source/Schema.NET/ContextJsonConverter.cs b/Source/Schema.NET/ContextJsonConverter.cs new file mode 100644 index 00000000..24c03075 --- /dev/null +++ b/Source/Schema.NET/ContextJsonConverter.cs @@ -0,0 +1,59 @@ +namespace Schema.NET +{ + using System; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + /// + /// Converts a object to and from JSON. + /// + /// + public class ContextJsonConverter : JsonConverter + { + /// + public override JsonLdContext ReadJson(JsonReader reader, Type objectType, JsonLdContext existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var context = hasExistingValue ? existingValue : new JsonLdContext(); + + string name; + string language; + if (reader.TokenType == JsonToken.String) + { + name = (string)reader.Value; + language = null; + } + else + { + var o = JObject.Load(reader); + + var nameProperty = o.Property("name", StringComparison.OrdinalIgnoreCase); + name = nameProperty?.Value?.ToString() ?? "http://schema.org"; + + var languageProperty = o.Property("@language", StringComparison.OrdinalIgnoreCase); + language = languageProperty?.Value?.ToString(); + } + + context.Name = name; + context.Language = language; + return context; + } + + /// + public override void WriteJson(JsonWriter writer, JsonLdContext value, JsonSerializer serializer) + { + if (string.IsNullOrWhiteSpace(value.Language)) + { + writer.WriteValue(value.Name); + } + else + { + writer.WriteStartObject(); + writer.WritePropertyName("name"); + writer.WriteValue(value.Name); + writer.WritePropertyName("@language"); + writer.WriteValue(value.Language); + writer.WriteEndObject(); + } + } + } +} diff --git a/Source/Schema.NET/JsonLdContext.cs b/Source/Schema.NET/JsonLdContext.cs new file mode 100644 index 00000000..2405d0d4 --- /dev/null +++ b/Source/Schema.NET/JsonLdContext.cs @@ -0,0 +1,77 @@ +namespace Schema.NET +{ + using System; + using System.Runtime.Serialization; + + /// + /// The @context for a JSON-LD document. + /// See https://w3c.github.io/json-ld-syntax + /// + public class JsonLdContext : IEquatable + { + /// + /// Gets or sets the name. + /// + [DataMember(Name = "name", Order = 0)] + public string Name { get; set; } = "http://schema.org"; + + /// + /// Gets or sets the language. + /// + [DataMember(Name = "@language", Order = 1)] + public string Language { get; set; } + + /// + /// Performs an implicit conversion from to . + /// + /// The context. + /// The result of the conversion. + public static implicit operator string(JsonLdContext context) => context.Name; + + /// + /// Implements the operator ==. + /// + /// The left. + /// The right. + /// + /// The result of the operator. + /// + public static bool operator ==(JsonLdContext left, JsonLdContext right) => left.Equals(right); + + /// + /// Implements the operator !=. + /// + /// The left. + /// The right. + /// + /// The result of the operator. + /// + public static bool operator !=(JsonLdContext left, JsonLdContext right) => !(left == right); + + /// + public bool Equals(JsonLdContext other) + { + if (other is null) + { + return false; + } + + if (object.ReferenceEquals(this, other)) + { + return true; + } + + return this.Name == other.Name && + this.Language == other.Language; + } + + /// + public override bool Equals(object obj) => this.Equals(obj as JsonLdContext); + + /// + public override int GetHashCode() => HashCode.Of(this.Name).And(this.Language); + + /// + public override string ToString() => this.Name; + } +} diff --git a/Source/Schema.NET/JsonLdObject.cs b/Source/Schema.NET/JsonLdObject.cs index f467ae0b..a3731c71 100644 --- a/Source/Schema.NET/JsonLdObject.cs +++ b/Source/Schema.NET/JsonLdObject.cs @@ -1,17 +1,18 @@ -namespace Schema.NET +namespace Schema.NET { using System; - using System.Runtime.Serialization; - + using System.Runtime.Serialization; + using Newtonsoft.Json; + /// - /// The base object + /// The base JSON-LD object. /// See https://json-ld.org/spec/latest/json-ld /// [DataContract] public class JsonLdObject { /// - /// Gets or sets the context used to define the short-hand names that are used throughout a JSON-LD document. + /// Gets the context used to define the short-hand names that are used throughout a JSON-LD document. /// These short-hand names are called terms and help developers to express specific identifiers in a compact /// manner. /// When two people communicate with one another, the conversation takes place in a shared environment, @@ -22,8 +23,16 @@ public class JsonLdObject /// Simply speaking, a context is used to map terms to IRIs. Terms are case sensitive and any valid string that /// is not a reserved JSON-LD keyword can be used as a term. /// - [DataMember(Name = "@context", Order = 0)] - public virtual string Context { get; } + [DataMember(Name = "@context", Order = 0)] + [JsonConverter(typeof(ContextJsonConverter))] + public virtual JsonLdContext Context { get; internal set; } = new JsonLdContext(); + + /// + /// Gets or sets the type, used to uniquely identify things that are being described in the document with IRIs + /// or blank node identifiers. + /// + [DataMember(Name = "@type", Order = 1)] + public virtual string Type { get; } /// /// Gets or sets the identifier used to uniquely identify things that are being described in the document with @@ -35,12 +44,5 @@ public class JsonLdObject /// [DataMember(Name = "@id", Order = 2)] public virtual Uri Id { get; set; } - - /// - /// Gets or sets the type, used to uniquely identify things that are being described in the document with IRIs - /// or blank node identifiers. - /// - [DataMember(Name = "@type", Order = 1)] - public virtual string Type { get; } } } diff --git a/Source/Schema.NET/Schema.NET.csproj b/Source/Schema.NET/Schema.NET.csproj index 629552fe..c01f555f 100644 --- a/Source/Schema.NET/Schema.NET.csproj +++ b/Source/Schema.NET/Schema.NET.csproj @@ -43,7 +43,7 @@ - + diff --git a/Source/Schema.NET/ValuesJsonConverter.cs b/Source/Schema.NET/ValuesJsonConverter.cs index 16f98d81..03925732 100644 --- a/Source/Schema.NET/ValuesJsonConverter.cs +++ b/Source/Schema.NET/ValuesJsonConverter.cs @@ -10,7 +10,7 @@ namespace Schema.NET using Newtonsoft.Json.Linq; /// - /// Converts a object to JSON. + /// Converts a object to and from JSON. /// /// public class ValuesJsonConverter : JsonConverter diff --git a/Source/Schema.NET/core/Thing.cs b/Source/Schema.NET/core/Thing.cs index c1cd0ac4..f0ecfd6f 100644 --- a/Source/Schema.NET/core/Thing.cs +++ b/Source/Schema.NET/core/Thing.cs @@ -1,4 +1,4 @@ -namespace Schema.NET +namespace Schema.NET { using System; using System.Runtime.Serialization; @@ -76,12 +76,6 @@ public partial interface IThing [DataContract] public partial class Thing : IThing { - /// - /// Gets the context for the object, specifying that it comes from schema.org. - /// - [DataMember(Name = "@context", Order = 0)] - public override string Context => "http://schema.org"; - /// /// Gets the name of the type as specified by schema.org. /// diff --git a/Tests/Schema.NET.Test/ContextJsonConverterTest.cs b/Tests/Schema.NET.Test/ContextJsonConverterTest.cs new file mode 100644 index 00000000..80aea47c --- /dev/null +++ b/Tests/Schema.NET.Test/ContextJsonConverterTest.cs @@ -0,0 +1,63 @@ +namespace Schema.NET.Test +{ + using Newtonsoft.Json; + using Xunit; + + public class ContextJsonConverterTest + { + [Fact] + public void ReadJson_StringContext_ContextHasName() + { + var json = "{\"@context\":\"foo\",\"@type\":\"Thing\"}"; + + var thing = JsonConvert.DeserializeObject(json); + + Assert.NotNull(thing.Context); + Assert.Equal("foo", thing.Context.Name); + Assert.Null(thing.Context.Language); + } + + [Fact] + public void ReadJson_ObjectContextWithName_ContextHasName() + { + var json = "{\"@context\":{\"name\":\"foo\"},\"@type\":\"Thing\"}"; + + var thing = JsonConvert.DeserializeObject(json); + + Assert.NotNull(thing.Context); + Assert.Equal("foo", thing.Context.Name); + Assert.Null(thing.Context.Language); + } + + [Fact] + public void ReadJson_ObjectContextWithNameAndLanguage_ContextHasNameAndLanguage() + { + var json = "{\"@context\":{\"name\":\"foo\",\"@language\":\"en\"},\"@type\":\"Thing\"}"; + + var thing = JsonConvert.DeserializeObject(json); + + Assert.NotNull(thing.Context); + Assert.Equal("foo", thing.Context.Name); + Assert.Equal("en", thing.Context.Language); + } + + [Fact] + public void WriteJson_StringContext_ContextHasName() + { + var json = new Thing().ToString(); + + Assert.Equal("{\"@context\":\"http://schema.org\",\"@type\":\"Thing\"}", json); + } + + [Fact] + public void WriteJson_ObjectContextWithLanguage_ContextHasName() + { + var thing = new Thing(); + thing.Context.Language = "en"; + + var json = thing.ToString(); + + Assert.Equal("{\"@context\":{\"name\":\"http://schema.org\",\"@language\":\"en\"},\"@type\":\"Thing\"}", json); + } + } +} diff --git a/Tests/Schema.NET.Test/JsonLdContextTest.cs b/Tests/Schema.NET.Test/JsonLdContextTest.cs new file mode 100644 index 00000000..8d072de1 --- /dev/null +++ b/Tests/Schema.NET.Test/JsonLdContextTest.cs @@ -0,0 +1,67 @@ +namespace Schema.NET.Test +{ + using System.Collections.Generic; + using Xunit; + + public class JsonLdContextTest + { + public static IEnumerable EqualContexts => new List + { + new object[] { new JsonLdContext(), new JsonLdContext() }, + new object[] { new JsonLdContext() { Name = "a" }, new JsonLdContext() { Name = "a" } }, + new object[] + { + new JsonLdContext() { Name = "a", Language = "b" }, + new JsonLdContext() { Name = "a", Language = "b" }, + }, + }; + + public static IEnumerable NotEqualContexts => new List + { + new object[] { new JsonLdContext(), null }, + new object[] { new JsonLdContext(), new JsonLdContext() { Name = "a" } }, + new object[] { new JsonLdContext() { Name = "a" }, new JsonLdContext() }, + new object[] { new JsonLdContext() { Name = "a" }, new JsonLdContext() { Name = "b" } }, + new object[] + { + new JsonLdContext() { Name = "a", Language = "b" }, + new JsonLdContext() { Name = "a", Language = "c" }, + }, + }; + + public static IEnumerable ToStringContexts => new List + { + new object[] { new JsonLdContext(), "http://schema.org" }, + new object[] { new JsonLdContext() { Name = "a" }, "a" }, + }; + + [Theory] + [MemberData(nameof(EqualContexts))] + public void Equals_IsEqual_ReturnsTrue(JsonLdContext a, JsonLdContext b) => Assert.True(a.Equals(b)); + + [Theory] + [MemberData(nameof(NotEqualContexts))] + public void Equals_IsNotEqual_ReturnsFalse(JsonLdContext a, JsonLdContext b) => Assert.False(a.Equals(b)); + + [Theory] + [MemberData(nameof(EqualContexts))] + public void EqualsOperator_IsEqual_ReturnsTrue(JsonLdContext a, JsonLdContext b) => Assert.True(a == b); + + [Theory] + [MemberData(nameof(NotEqualContexts))] + public void EqualsOperator_IsNotEqual_ReturnsFalse(JsonLdContext a, JsonLdContext b) => Assert.False(a == b); + + [Theory] + [MemberData(nameof(EqualContexts))] + public void NotEqualsOperator_IsEqual_ReturnsFalse(JsonLdContext a, JsonLdContext b) => Assert.False(a != b); + + [Theory] + [MemberData(nameof(NotEqualContexts))] + public void NotEqualsOperator_IsNotEqual_ReturnsTrue(JsonLdContext a, JsonLdContext b) => Assert.True(a != b); + + [Theory] + [MemberData(nameof(ToStringContexts))] + public void ToString_(JsonLdContext context, string expectedValue) => + Assert.Equal(expectedValue, context.ToString()); + } +}