diff --git a/Core/Data/Result/ConciseModel.cs b/Core/Data/Result/ConciseModel.cs index a12cf59..c47b7a2 100644 --- a/Core/Data/Result/ConciseModel.cs +++ b/Core/Data/Result/ConciseModel.cs @@ -6,6 +6,7 @@ using System.Runtime.Serialization; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Threading.Tasks; @@ -363,6 +364,15 @@ public bool ContainKeyword(string value, bool ignoreCase = false) return false; } + /// + /// Converts to JSON object. + /// + /// Options to control the reader behavior during parsing. + /// A JSON object instance. + /// Its property does not represent a valid single JSON object. + public virtual JsonObjectNode ToJson(JsonSerializerOptions options = default) + => JsonObjectNode.ConvertFrom(this, options); + /// /// Returns a string that represents the current model. /// diff --git a/Core/Data/Result/DataResult.cs b/Core/Data/Result/DataResult.cs index a850269..4d11f1d 100644 --- a/Core/Data/Result/DataResult.cs +++ b/Core/Data/Result/DataResult.cs @@ -240,6 +240,14 @@ public JsonDataResult(JsonObjectNode data, string message) [Description("The additional information of the result.")] public JsonObjectNode AdditionalInfo { get; set; } + /// + /// Gets or sets the components. + /// + [DataMember(Name = "components")] + [JsonPropertyName("components")] + [Description("The components for reference.")] + public JsonObjectNode Components { get; set; } + /// /// Gets a value indicating whether the data is null. /// @@ -345,6 +353,57 @@ public IJsonDataNode TryGetValue(string key, string subKey, params string[] keyP /// The property does not exist. public IJsonDataNode TryGetValue(ReadOnlySpan key) => Data?.TryGetValue(key); + + /// + /// Gets schema. + /// + /// The schema key. + /// The schema information. + public JsonObjectNode GetSchema(string key) + => string.IsNullOrWhiteSpace(key) ? null : GetComponent("schemas")?.TryGetObjectValue(key); + + /// + /// Gets reference object. + /// + /// The type or group key of the resource. + /// The identifier of the object. + /// The reference object. + public JsonObjectNode GetReferenceObject(string type, string id) + { + if (string.IsNullOrWhiteSpace(id)) return null; + var components = GetComponents(); + if (components == null) return null; + var valueKind = components.GetValueKind(type); + var json = valueKind switch + { + JsonValueKind.Object => components.TryGetObjectValue(type)?.TryGetObjectValue(id), + JsonValueKind.Array => components.TryGetArrayValue(type)?.TryGetObjectValueById(id), + _ => null, + }; + if (json == null) return null; + var refPath = json.TryGetStringValue("$ref"); + if (refPath == null || json.Count != 1) return json; + return JsonObjectNode.TryGetRefObjectValue(Data, json, ToJson()); + } + + internal JsonObjectNode GetComponents() + => Components ?? Data?.TryGetObjectValue("components"); + + internal JsonObjectNode GetComponent(string key) + { + var dict = GetComponents(); + return string.IsNullOrWhiteSpace(key) ? dict : dict?.TryGetObjectValue(key); + } + + internal JsonObjectNode ToJson() + => new() + { + { "track", TrackingId }, + { "message", Message }, + { "data", Data }, + { "info", AdditionalInfo }, + { "components", Components } + }; } /// diff --git a/Core/Net/Http/JsonHttpClient.cs b/Core/Net/Http/JsonHttpClient.cs index 788e32b..6f46652 100644 --- a/Core/Net/Http/JsonHttpClient.cs +++ b/Core/Net/Http/JsonHttpClient.cs @@ -24,6 +24,20 @@ namespace Trivial.Net; +/// +/// The maker to create JSON HTTP client. +/// +public interface IJsonHttpClientMaker +{ + /// + /// Creates a JSON HTTP client. + /// + /// The type of response. + /// An optional callback raised on data received. + /// A new JSON HTTP client. + JsonHttpClient Create(Action> callback = null); +} + /// /// The event arguments on sending. /// diff --git a/Core/Security/Token/OAuth.cs b/Core/Security/Token/OAuth.cs index 5c5a78f..675d680 100644 --- a/Core/Security/Token/OAuth.cs +++ b/Core/Security/Token/OAuth.cs @@ -23,7 +23,7 @@ namespace Trivial.Security; /// The OAuth HTTP web client (for RFC-6749). /// You can use this to login and then create the JSON HTTP web clients with the authentication information. /// -public class OAuthClient : TokenContainer +public class OAuthClient : TokenContainer, IJsonHttpClientMaker { /// /// The app accessing key instance. @@ -699,7 +699,7 @@ private HttpClient CreateHttpClient() /// /// The OAuth based JSON HTTP web client. /// -public abstract class OAuthBasedClient : TokenContainer +public abstract class OAuthBasedClient : TokenContainer, IJsonHttpClientMaker { /// /// The OAuth client. diff --git a/Core/Text/Json/ArrayNode.cs b/Core/Text/Json/ArrayNode.cs index 9a740d3..3dde043 100644 --- a/Core/Text/Json/ArrayNode.cs +++ b/Core/Text/Json/ArrayNode.cs @@ -6,11 +6,13 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Security; using System.Security.Cryptography; using System.Text; using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using Trivial.Collection; @@ -1686,6 +1688,41 @@ public bool TryGetObjectValue(int index, out JsonObjectNode result) return v is not null; } + /// + /// Tries to get the value at the specific index. + /// + /// The identifier of the object. + /// The value. + public JsonObjectNode TryGetObjectValueById(string id) + => TryGetObjectValueById(id, out var p) ? p : null; + + /// + /// Tries to get the value at the specific index. + /// + /// The identifier of the object. + /// The result. + /// true if has the object; otherwise, false. + public bool TryGetObjectValueById(string id, out JsonObjectNode result) + { + id = id?.Trim(); + if (string.IsNullOrEmpty(id)) + { + result = null; + return false; + } + + foreach (var item in store) + { + if (item is not JsonObjectNode json) continue; + if (json.Id?.Trim() != id && json.TryGetStringTrimmedValue("id") != id) continue; + result = json; + return true; + } + + result = null; + return false; + } + /// /// Tries to get the value at the specific index. /// @@ -1693,22 +1730,124 @@ public bool TryGetObjectValue(int index, out JsonObjectNode result) /// The root node. /// The value. public JsonObjectNode TryGetRefObjectValue(int index, JsonObjectNode root) + => JsonObjectNode.TryGetRefObjectValue(null, TryGetObjectValue(index), root); + + /// + /// Tries to get the value at the specific index. + /// + /// The zero-based index of the element to get. + /// The root node. + /// The value. + public JsonObjectNode TryGetRefObjectValue(int index, JsonDataResult root) + => root == null ? null : TryGetRefObjectValue(index, root.ToJson()); + + /// + /// Filters by the given property. + /// + /// The property key. + /// A list of value matched. + public IList WithProperty(string key) { - var json = TryGetObjectValue(index); - if (json == null) return null; - var refPath = json.TryGetStringValue("$ref"); - if (string.IsNullOrWhiteSpace(refPath)) return json; - if (root is null || refPath == JsonValues.SELF_REF) return null; - if (refPath.StartsWith("#/")) + var list = new List(); + var col = SelectObjects(); + foreach (var item in col) { -#pragma warning disable IDE0057 - var path = refPath.Substring(2).Split('/'); -#pragma warning restore IDE0057 - return root.TryGetObjectValue(path); + if (item.ContainsKey(key)) list.Add(item); } - if (refPath == "$") return root; - return root.TryGetValue(refPath, true) as JsonObjectNode; + return list; + } + + /// + /// Filters by the given property. + /// + /// The property key. + /// The value kind of the property. + /// A list of value matched. + public IList WithProperty(string key, JsonValueKind kind) + { + var list = new List(); + var col = SelectObjects(); + foreach (var item in col) + { + if (item.GetValueKind(key) == kind) list.Add(item); + } + + return list; + } + + /// + /// Filters by the given property. + /// + /// The property key. + /// The value of the property. + /// A list of value matched. + public IList WithProperty(string key, string value) + { + var list = new List(); + var col = SelectObjects(); + foreach (var item in col) + { + if (item.TryGetStringValue(key) == value) list.Add(item); + } + + return list; + } + + /// + /// Filters by the given property. + /// + /// The property key. + /// The value of the property. + /// One of the enumeration values that specifies how the strings will be compared. + /// A list of value matched. + public IList WithProperty(string key, string value, StringComparison comparisonType) + { + if (value == null) return WithProperty(key, value); + var list = new List(); + var col = SelectObjects(); + foreach (var item in col) + { + if (value.Equals(item.TryGetStringValue(key), comparisonType)) list.Add(item); + } + + return list; + } + + /// + /// Filters by the given property. + /// + /// The property key. + /// The value of the property. + /// A list of value matched. + public IList WithProperty(string key, int value) + { + var list = new List(); + var col = SelectObjects(); + foreach (var item in col) + { + if (item.TryGetInt32Value(key) == value) list.Add(item); + } + + return list; + } + + /// + /// Filters by the given property. + /// + /// The property key. + /// The value of the property. + /// A list of value matched. + public IList WithProperty(string key, bool value) + { + var list = new List(); + var col = SelectObjects(); + foreach (var item in col) + { + if (item.TryGetBooleanValue(key) == value) list.Add(item); + } + + return list; } /// @@ -4050,6 +4189,13 @@ public override string ToString() public string ToString(IndentStyles indentStyle) => ConvertToString(indentStyle, 0); + /// + /// Filters by getting JSON object list only. + /// + /// A list of value matched. + internal IList SelectObjects() + => store.OfType().ToList(); + /// /// Gets the JSON array format string of the value. /// @@ -4952,6 +5098,57 @@ public static JsonArrayNode TryParse(string json, JsonDocumentOptions options = return null; } + /// + /// Tries to parse a string to a JSON array. + /// + /// A file with JSON array string content to parse. + /// Options to control the reader behavior during parsing. + /// A JSON array instance; or null, if error format. + public static JsonArrayNode TryParse(FileInfo file, JsonDocumentOptions options = default) + { + try + { + if (file == null || !file.Exists) return null; + using var stream = file.OpenRead(); + return Parse(stream, options); + } + catch (ArgumentException) + { + } + catch (InvalidOperationException) + { + } + catch (JsonException) + { + } + catch (FormatException) + { + } + catch (InvalidCastException) + { + } + catch (IOException) + { + } + catch (SecurityException) + { + } + catch (UnauthorizedAccessException) + { + } + catch (NullReferenceException) + { + } + catch (AggregateException) + { + } + catch (ExternalException) + { + } + + return null; + } + /// /// Converts an object to JSON object. /// diff --git a/Core/Text/Json/ObjectNode.cs b/Core/Text/Json/ObjectNode.cs index afa1b2f..8dc8be1 100644 --- a/Core/Text/Json/ObjectNode.cs +++ b/Core/Text/Json/ObjectNode.cs @@ -16,6 +16,7 @@ using Trivial.Data; using Trivial.Maths; +using Trivial.Net; using Trivial.Reflection; using Trivial.Web; @@ -184,7 +185,7 @@ private JsonObjectNode(IDictionary copy, bool threadSafe /// public string Id { - get => TryGetStringValue("$id"); + get => TryGetStringValue("$id")?.Trim(); set => SetValueOrRemove("$id", value); } @@ -2650,22 +2651,16 @@ public bool TryGetObjectValue(IEnumerable keyPath, out JsonObjectNode re /// The root node. /// The value. public JsonObjectNode TryGetRefObjectValue(string key, JsonObjectNode root) - { - root ??= this; - var json = TryGetObjectValue(key); - if (json == null) return null; - var refPath = json.TryGetStringValue("$ref"); - if (string.IsNullOrWhiteSpace(refPath)) return json; - if (refPath == JsonValues.SELF_REF) return this; - if (refPath.StartsWith("#/")) - { - var path = refPath.Substring(2).Split('/'); - return root.TryGetObjectValue(path); - } + => TryGetRefObjectValue(this, TryGetObjectValue(key), root); - if (refPath == "$") return root; - return root.TryGetValue(refPath, true) as JsonObjectNode; - } + /// + /// Tries to get the value of the specific property. + /// + /// The property key. + /// The root node. + /// The value. + public JsonObjectNode TryGetRefObjectValue(string key, JsonDataResult root) + => root == null ? null : TryGetRefObjectValue(key, root.ToJson()); /// /// Tries to get the value of the specific property. @@ -7629,7 +7624,7 @@ public static JsonObjectNode TryParse(FileInfo file, JsonDocumentOptions options /// The object to convert. /// Options to control the reader behavior during parsing. /// A JSON object instance. - /// json does not represent a valid single JSON object. + /// obj or its property does not represent a valid single JSON object. public static JsonObjectNode ConvertFrom(object obj, JsonSerializerOptions options = default) { if (obj is null) return null; @@ -7714,6 +7709,78 @@ public static JsonObjectNode ConvertFrom(object obj, JsonSerializerOptions optio return !leftValue.Equals(rightValue); } + /// + /// Tries to get the value of the specific property. + /// + /// The source. + /// The property. + /// The root node. + /// The value. + internal static JsonObjectNode TryGetRefObjectValue(JsonObjectNode source, JsonObjectNode property, JsonObjectNode root) + { + root ??= source; + if (property == null) return null; + var refPath = property.TryGetStringValue("$ref"); + if (refPath == null) return property; + if (string.IsNullOrWhiteSpace(refPath) || refPath == "#") return root; + if (refPath == JsonValues.SELF_REF) return source; + if (refPath.StartsWith("#/")) + { + var path = refPath.Substring(2).Split('/'); + return root?.TryGetObjectValue(path); + } + + if (refPath == "$") return root; + if (refPath.StartsWith("./")) + { + try + { + var file = new FileInfo(refPath); + return TryParse(file); + } + catch (ArgumentException) + { + } + catch (UnauthorizedAccessException) + { + } + catch (SecurityException) + { + } + catch (NotSupportedException) + { + } + catch (IOException) + { + } + catch (ExternalException) + { + } + + return property; + } + + if (refPath.StartsWith("http") && refPath.Contains("://")) + { + property = new JsonObjectNode + { + { "$ref", refPath } + }; + _ = TryParse(null, refPath, property); + return property; + } + + return root?.TryGetValue(refPath, true) as JsonObjectNode; + } + + internal static async Task TryParse(JsonHttpClient http, string url, JsonObjectNode json) + { + var resp = await (http ?? new()).GetAsync(url); + if (json == null) json = new(); + else json.Clear(); + json.SetRange(resp); + } + private static JsonObjectNode TryGetObjectValueByProperty(JsonObjectNode json, string key) { if (json.GetValueKind(key) == JsonValueKind.Array)