Merge pull request #3413 from jlewin/design_tools

Components prototype
This commit is contained in:
johnlewin 2018-06-12 15:42:48 -07:00 committed by GitHub
commit ba4f367be8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 706 additions and 27 deletions

View file

@ -0,0 +1,48 @@
/*
Copyright (c) 2018, Lars Brubaker, John Lewin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
*/
using System.Collections.Generic;
using MatterHackers.DataConverters3D;
namespace MatterHackers.MatterControl.DesignTools
{
public class ComponentObject3D : Object3D
{
public ComponentObject3D()
{
}
public ComponentObject3D(IEnumerable<IObject3D> children)
: base (children)
{
}
public List<string> SurfacedEditors { get; set; } = new List<string>();
}
}

View file

@ -27,14 +27,11 @@ of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
*/
using System.Threading;
using MatterHackers.Agg;
using MatterHackers.Agg.UI;
using MatterHackers.Agg.VertexSource;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.DesignTools.Operations;
using MatterHackers.PolygonMesh;
using MatterHackers.VectorMath;
namespace MatterHackers.MatterControl.DesignTools

View file

@ -51,11 +51,14 @@ namespace MatterHackers.MatterControl.DesignTools
public class EditableProperty
{
public IObject3D Item { get; private set; }
public object source;
public PropertyInfo PropertyInfo { get; private set; }
public EditableProperty(PropertyInfo p, IObject3D item)
public EditableProperty(PropertyInfo p, object source)
{
this.Item = item;
this.source = source;
this.Item = source as IObject3D;
this.PropertyInfo = p;
}
@ -71,7 +74,7 @@ namespace MatterHackers.MatterControl.DesignTools
return nameAttribute?.DisplayName ?? prop.Name.SplitCamelCase();
}
public object Value => PropertyInfo.GetGetMethod().Invoke(Item, null);
public object Value => PropertyInfo.GetGetMethod().Invoke(source, null);
/// <summary>
/// Use reflection to set property value
@ -79,7 +82,7 @@ namespace MatterHackers.MatterControl.DesignTools
/// <param name="value"></param>
public void SetValue(object value)
{
this.PropertyInfo.GetSetMethod().Invoke(this.Item, new Object[] { value });
this.PropertyInfo.GetSetMethod().Invoke(source, new Object[] { value });
}
public string DisplayName => GetDisplayName(PropertyInfo);

View file

@ -100,6 +100,7 @@
<Compile Include="DesignTools\Operations\Image\MapOnMaxIntensity.cs" />
<Compile Include="DesignTools\Operations\OperationSource.cs" />
<Compile Include="DesignTools\Operations\PinchObject3D.cs" />
<Compile Include="DesignTools\Primitives\ComponentObject3D.cs" />
<Compile Include="DesignTools\Primitives\CubeObject3D.cs" />
<Compile Include="DesignTools\Primitives\HalfCylinderObject3D.cs" />
<Compile Include="DesignTools\Primitives\HalfWedgeObject3D.cs" />
@ -425,6 +426,7 @@
<Compile Include="SlicerConfiguration\Settings\PrinterSettings.cs" />
<Compile Include="DataStorage\Classic\ClassicSqlitePrinterProfiles.cs" />
<Compile Include="Utilities\AuthenticationData.cs" />
<Compile Include="Utilities\JsonPath.cs" />
<Compile Include="Utilities\LimitCallFrequency.cs" />
<Compile Include="Utilities\SelectedListItems.cs" />
<Compile Include="EeProm\EePromMarlinSettings.cs" />

View file

@ -30,6 +30,8 @@ either expressed or implied, of the FreeBSD Project.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JsonPath;
using MatterHackers.Agg;
using MatterHackers.Agg.Platform;
using MatterHackers.Agg.UI;
@ -39,6 +41,7 @@ using MatterHackers.MatterControl.CustomWidgets;
using MatterHackers.MatterControl.DesignTools;
using MatterHackers.MatterControl.Library;
using MatterHackers.VectorMath;
using static JsonPath.JsonPathContext.ReflectionValueSystem;
namespace MatterHackers.MatterControl.PartPreviewWindow
{
@ -280,9 +283,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
public GuiWidget ContentPanel { get; set; }
JsonPathContext xpathLikeResolver = new JsonPathContext();
public void SetActiveItem(IObject3D selectedItem)
{
var selectedItemType = selectedItem.GetType();
editButton.Enabled = (selectedItem.Children.Count > 0);
@ -295,7 +299,54 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
var activeEditors = new List<(IObject3DEditor, IObject3D, string)>();
foreach (var child in new[] { selectedItem })
var editableItems = new List<IObject3D> { selectedItem };
var undoBuffer = sceneContext.Scene.UndoBuffer;
editorPanel.CloseAllChildren();
bool allowOperations = true;
if (selectedItem is ComponentObject3D componentObject)
{
allowOperations = false;
foreach (var selector in componentObject.SurfacedEditors)
{
// Get the named property via reflection
// Selector example: '$.Children<CylinderObject3D>'
var match = xpathLikeResolver.Select(componentObject, selector).ToList();
//// TODO: Create editor row for each property
//// - Use the type of the property to find a matching editor (ideally all datatype -> editor functionality would resolve consistently)
//// - Add editor row for each
foreach (var instance in match)
{
if (instance is IObject3D object3D)
{
editableItems.Add(object3D);
}
else if (JsonPath.JsonPathContext.ReflectionValueSystem.LastMemberValue is ReflectionTarget reflectionTarget)
{
var context = new PPEContext()
{
item = item
};
var editableProperty = new EditableProperty(reflectionTarget.PropertyInfo, reflectionTarget.Source);
var editor = PublicPropertyEditor.CreatePropertyEditor(editableProperty, undoBuffer, context, theme);
if (editor != null)
{
editorPanel.AddChild(editor);
}
}
}
}
}
foreach (var child in editableItems)
{
if (ApplicationController.Instance.GetEditorsForType(child.GetType())?.FirstOrDefault() is IObject3DEditor editor)
{
@ -303,7 +354,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
}
}
ShowObjectEditor(activeEditors, selectedItem);
ShowObjectEditor(activeEditors, selectedItem, allowOperations: allowOperations);
}
private class OperationButton :TextButton
@ -324,10 +375,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
}
}
private void ShowObjectEditor(IEnumerable<(IObject3DEditor editor, IObject3D item, string displayName)> scope, IObject3D rootSelection)
private void ShowObjectEditor(IEnumerable<(IObject3DEditor editor, IObject3D item, string displayName)> scope, IObject3D rootSelection, bool allowOperations = true)
{
editorPanel.CloseAllChildren();
if (scope == null)
{
return;
@ -366,25 +415,28 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
var buttons = new List<OperationButton>();
foreach (var nodeOperation in ApplicationController.Instance.Graph.Operations)
if (allowOperations)
{
foreach (var type in nodeOperation.MappedTypes)
foreach (var nodeOperation in ApplicationController.Instance.Graph.Operations)
{
if (type.IsAssignableFrom(selectedItemType)
&& (nodeOperation.IsVisible == null || nodeOperation.IsVisible(selectedItem)))
foreach (var type in nodeOperation.MappedTypes)
{
var button = new OperationButton(nodeOperation, selectedItem, theme)
if (type.IsAssignableFrom(selectedItemType)
&& (nodeOperation.IsVisible == null || nodeOperation.IsVisible(selectedItem)))
{
BackgroundColor = theme.MinimalShade,
Margin = theme.ButtonSpacing
};
button.EnsureAvailablity();
button.Click += (s, e) =>
{
nodeOperation.Operation(selectedItem, sceneContext.Scene).ConfigureAwait(false);
};
var button = new OperationButton(nodeOperation, selectedItem, theme)
{
BackgroundColor = theme.MinimalShade,
Margin = theme.ButtonSpacing
};
button.EnsureAvailablity();
button.Click += (s, e) =>
{
nodeOperation.Operation(selectedItem, sceneContext.Scene).ConfigureAwait(false);
};
buttons.Add(button);
buttons.Add(button);
}
}
}
}

577
Utilities/JsonPath.cs Normal file
View file

@ -0,0 +1,577 @@
#region Copyright (c) 2007 Atif Aziz. All rights reserved.
// https://github.com/atifaziz/JSONPath
//
// C# implementation of JSONPath[1]
// [1] http://goessner.net/articles/JsonPath/
//
// The MIT License
//
// Copyright (c) 2007 Atif Aziz . All rights reserved.
// Portions Copyright (c) 2007 Stefan Goessner (goessner.net)
// 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
namespace JsonPath
{
#region Imports
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using MatterHackers.DataConverters3D;
using Newtonsoft.Json.Linq;
#endregion
public interface IJsonPathValueSystem
{
bool HasMember(object value, string member);
object GetMemberValue(object value, string member);
IEnumerable<string> GetMembers(object value);
bool IsObject(object value);
bool IsArray(object value);
bool IsPrimitive(object value);
}
public sealed class JsonPathContext
{
public static readonly JsonPathContext Default = new JsonPathContext();
public Func<string /* script */,
object /* value */,
string /* context */,
object /* result */>
ScriptEvaluator { get; set; }
public IJsonPathValueSystem ValueSystem { get; set; }
public IEnumerable<object> Select(object obj, string expr) =>
SelectNodes(obj, expr, (v, _) => v);
public IEnumerable<T> SelectNodes<T>(object obj, string expr, Func<object, string, T> resultor)
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
if (resultor == null) throw new ArgumentNullException(nameof(resultor));
var i = new Interpreter(ValueSystem, ScriptEvaluator);
expr = Normalize(expr);
if (expr.Length >= 1 && expr[0] == '$') // ^\$:?
expr = expr.Substring(expr.Length >= 2 && expr[1] == ';' ? 2 : 1);
return i.Trace(expr, obj, "$", (value, path) => resultor(value, AsBracketNotation(path)));
}
static string Normalize(string expr)
{
var subx = new List<string>();
expr = RegExp.Replace(expr, @"[\['](\??\(.*?\))[\]']", m =>
{
subx.Add(m.Groups[1].Value);
return "[#" + (subx.Count - 1).ToString(CultureInfo.InvariantCulture) + "]";
});
expr = RegExp.Replace(expr, @"'?\.'?|\['?", ";");
expr = RegExp.Replace(expr, @";;;|;;", ";..;");
expr = RegExp.Replace(expr, @";$|'?\]|'$", string.Empty);
expr = RegExp.Replace(expr, @"#([0-9]+)", m =>
{
var index = int.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
return subx[index];
});
return expr;
}
public static string AsBracketNotation(string[] indicies)
{
if (indicies == null)
throw new ArgumentNullException(nameof(indicies));
var sb = new StringBuilder();
foreach (var index in indicies)
{
if (sb.Length == 0)
{
sb.Append('$');
}
else
{
sb.Append('[');
if (RegExp.IsMatch(index, @"^[0-9*]+$"))
sb.Append(index);
else
sb.Append('\'').Append(index).Append('\'');
sb.Append(']');
}
}
return sb.ToString();
}
static int? TryParseInt(string str) =>
int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out var n)
? n : (int?) null;
sealed class Interpreter
{
readonly Func<string, object, string, object> _eval;
readonly IJsonPathValueSystem _system;
static readonly IJsonPathValueSystem DefaultValueSystem = new ReflectionValueSystem(); // new BasicValueSystem();
static readonly char[] Colon = { ':' };
static readonly char[] Semicolon = { ';' };
delegate void WalkCallback(object member, string loc, string expr, object value, string path);
public Interpreter(IJsonPathValueSystem valueSystem, Func<string, object, string, object> eval)
{
_eval = eval ?? delegate
{
// @ symbol in expr must be interpreted specially to resolve
// to value. In JavaScript, the implementation would look
// like:
//
// return obj && value && eval(expr.replace(/@/g, "value"));
return null;
};
_system = valueSystem ?? DefaultValueSystem;
}
sealed class TraceArgs
{
public readonly string Expr;
public readonly object Value;
public readonly string Path;
public TraceArgs(string expr, object value, string path)
{
Expr = expr;
Value = value;
Path = path;
}
}
public IEnumerable<T> Trace<T>(string expr, object value, string path, Func<object, string[], T> resultor) =>
Trace(Args(expr, value, path), resultor);
static TraceArgs Args(string expr, object value, string path) =>
new TraceArgs(expr, value, path);
IEnumerable<T> Trace<T>(TraceArgs args, Func<object, string[], T> resultor)
{
var stack = new Stack<TraceArgs>();
stack.Push(args);
while (stack.Count > 0)
{
var popped = stack.Pop();
var expr = popped.Expr;
var value = popped.Value;
var path = popped.Path;
if (string.IsNullOrEmpty(expr))
{
if (path != null)
yield return resultor(value, path.Split(Semicolon));
continue;
}
var i = expr.IndexOf(';');
var atom = i >= 0 ? expr.Substring(0, i) : expr;
var tail = i >= 0 ? expr.Substring(i + 1) : string.Empty;
if (value != null && _system.HasMember(value, atom))
{
stack.Push(Args(tail, Index(value, atom), path + ";" + atom));
}
else if (atom == "*")
{
Walk(atom, tail, value, path, (m, l, x, v, p) => stack.Push(Args(m + ";" + x, v, p)));
}
else if (atom == "..")
{
Walk(atom, tail, value, path, (m, l, x, v, p) =>
{
var result = Index(v, m.ToString());
if (result != null && !_system.IsPrimitive(result))
stack.Push(Args("..;" + x, result, p + ";" + m));
});
stack.Push(Args(tail, value, path));
}
else if (atom.Length > 2 && atom[0] == '(' && atom[atom.Length - 1] == ')') // [(exp)]
{
stack.Push(Args(_eval(atom, value, path.Substring(path.LastIndexOf(';') + 1)) + ";" + tail, value, path));
}
else if (atom.Length > 3 && atom[0] == '?' && atom[1] == '(' && atom[atom.Length - 1] == ')') // [?(exp)]
{
Walk(atom, tail, value, path, (m, l, x, v, p) =>
{
var result = _eval(RegExp.Replace(l, @"^\?\((.*?)\)$", "$1"),
Index(v, m.ToString()), m.ToString());
if (Convert.ToBoolean(result, CultureInfo.InvariantCulture))
stack.Push(Args(m + ";" + x, v, p));
});
}
else if (RegExp.IsMatch(atom, @"^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$")) // [start:end:step] Phyton slice syntax
{
foreach (var a in Slice(atom, tail, value, path).Reverse())
stack.Push(a);
}
else if (atom.IndexOf(',') >= 0) // [name1,name2,...]
{
foreach (var part in RegExp.Split(atom, @"'?,'?").Reverse())
stack.Push(Args(part + ";" + tail, value, path));
}
}
}
void Walk(string loc, string expr, object value, string path, WalkCallback callback)
{
if (_system.IsPrimitive(value))
return;
if (_system.IsArray(value))
{
var list = (IList) value;
for (var i = list.Count - 1; i >= 0; i--)
callback(i, loc, expr, value, path);
}
else if (_system.IsObject(value))
{
foreach (var key in _system.GetMembers(value).Reverse())
callback(key, loc, expr, value, path);
}
}
static IEnumerable<TraceArgs> Slice(string loc, string expr, object value, string path)
{
if (!(value is IList list))
yield break;
var length = list.Count;
var parts = loc.Split(Colon);
var start = TryParseInt(parts[0]) ?? 0;
var end = TryParseInt(parts[1]) ?? list.Count;
var step = parts.Length > 2 ? TryParseInt(parts[2]) ?? 1 : 1;
start = (start < 0) ? Math.Max(0, start + length) : Math.Min(length, start);
end = (end < 0) ? Math.Max(0, end + length) : Math.Min(length, end);
for (var i = start; i < end; i += step)
yield return Args(i + ";" + expr, value, path);
}
object Index(object obj, string member) =>
_system.GetMemberValue(obj, member);
}
static class RegExp
{
const RegexOptions Options = RegexOptions.ECMAScript;
public static bool IsMatch(string input, string pattern) =>
Regex.IsMatch(input, pattern, Options);
public static string Replace(string input, string pattern, string replacement) =>
Regex.Replace(input, pattern, replacement, Options);
public static string Replace(string input, string pattern, MatchEvaluator evaluator) =>
Regex.Replace(input, pattern, evaluator, Options);
public static IEnumerable<string> Split(string input, string pattern) =>
Regex.Split(input, pattern, Options);
}
public class ReflectionDetails
{
public List<PropertyInfo> Properties { get; set; }
}
public class ReflectionValueSystem : IJsonPathValueSystem
{
//private static Dictionary<string, ReflectionDetails> members = new Dictionary<string, ReflectionDetails>();
public const BindingFlags PublicInstanceProperties = BindingFlags.Public | BindingFlags.Instance;
public static ReflectionDetails GetMember(object item, string path)
{
//if (members.TryGetValue(path, out ReflectionDetails memberInfo))
//{
// return memberInfo;
//}
var reflectionDetails = new ReflectionDetails()
{
Properties = item.GetType().GetProperties(PublicInstanceProperties).Where(pi => pi.GetGetMethod() != null && pi.GetSetMethod() != null).ToList()
};
// members[path] = reflectionDetails;
return reflectionDetails;
}
public class ReflectionTarget
{
public PropertyInfo PropertyInfo { get; }
public object Source { get; }
public ReflectionTarget(PropertyInfo propertyInfo, object source)
{
this.PropertyInfo = propertyInfo;
this.Source = source;
}
}
public bool HasMember(object value, string member)
{
if (!IsPrimitive(value)
&& value is IDictionary dict)
{
return dict.Contains(member);
}
if (value is IList list)
{
return TryParseInt(member) is int i
&& i >= 0
&& i < list.Count;
}
string typeFilter;
// Separate member and typeFilter from member field
(member, typeFilter) = StripTypeFilter(member);
// IEnumerable field must be iterated to check
if (value is IEnumerable enumerable)
{
if (TryParseInt(member) is int)
{
return true;
}
// Handle the typeFilter case
foreach (var n in enumerable)
{
if (n.GetType().Name == typeFilter)
{
return true;
}
}
}
// TODO: Inline once troubleshooting is complete
var hasMember = GetMember(value, member).Properties.Any(p => p.Name == member);
return hasMember;
}
public static ReflectionTarget LastMemberValue { get; private set; }
public object GetMemberValue(object value, string member)
{
// Find and invoke property to get value
LastMemberValue = new ReflectionTarget(
GetMember(value, member).Properties.Where(p => p.Name == member).FirstOrDefault(),
value);
if (IsPrimitive(value))
{
throw new ArgumentException(null, nameof(value));
}
if (value is IDictionary dict)
{
return dict[member];
}
if (value is IList list
&& TryParseInt(member) is int i
&& i >= 0
&& i < list.Count)
{
return list[i];
}
string typeFilter;
// Separate member and typeFilter from member field
(member, typeFilter) = StripTypeFilter(member);
var propertyInfo = GetMember(value, member).Properties.Where(p => p.Name == member).FirstOrDefault();
LastMemberValue = new ReflectionTarget(propertyInfo, value);
if (value is IEnumerable enumerable)
{
if (TryParseInt(member) is int k)
{
var v = 0;
foreach (var n in enumerable)
{
if (v++ == k)
{
return n;
}
}
}
foreach (var n in enumerable)
{
if (n.GetType().Name == typeFilter)
{
return n;
}
}
}
var propertyValue = propertyInfo.GetGetMethod().Invoke(value, null);
if (!string.IsNullOrEmpty(typeFilter) && propertyValue is IEnumerable items)
{
foreach(var item in items)
{
if (item.GetType().Name == typeFilter)
{
return item;
}
}
}
return propertyValue;
}
public IEnumerable<string> GetMembers(object value) =>
((IDictionary)value).Keys.Cast<string>();
public bool IsObject(object value) => value is IDictionary;
public bool IsArray(object value) => value is IList;
public bool IsPrimitive(object value) =>
value == null
? throw new ArgumentNullException(nameof(value))
: Type.GetTypeCode(value.GetType()) != TypeCode.Object;
private static (string member, string filter) StripTypeFilter(string member)
{
int startFilter = member.IndexOf('<');
int endFilter = member.IndexOf('>');
int length = endFilter - startFilter;
string typeFilter = "";
if (startFilter != -1 && endFilter != -1 && length > 1)
{
typeFilter = member.Substring(startFilter + 1, length - 1);
member = member.Substring(0, startFilter);
}
return (member, typeFilter);
}
}
sealed class BasicValueSystem : IJsonPathValueSystem
{
public bool HasMember(object value, string member)
{
if (!IsPrimitive(value)
&& value is IDictionary dict)
{
return dict.Contains(member);
}
if (value is JArray array)
{
return TryParseInt(member) is int j
&& j >= 0
&& j < array.Count;
}
if (value is JToken token)
{
return token[member] != null;
}
return value is IList list
&& TryParseInt(member) is int i
&& i >= 0
&& i < list.Count;
}
public object GetMemberValue(object value, string member)
{
if (IsPrimitive(value))
{
throw new ArgumentException(null, nameof(value));
}
if (value is JArray array
&& TryParseInt(member) is int j
&& j >= 0
&& j < array.Count)
{
return array[j];
}
if (value is JToken token)
{
return token[member];
}
if (value is IDictionary dict)
{
return dict[member];
}
if (!(value is IList list))
{
throw new ArgumentException(nameof(value));
}
if (TryParseInt(member) is int i
&& i >= 0
&& i < list.Count)
{
return list[i];
}
return null;
}
public IEnumerable<string> GetMembers(object value) =>
((IDictionary) value).Keys.Cast<string>();
public bool IsObject(object value) => value is IDictionary;
public bool IsArray(object value) => value is IList;
public bool IsPrimitive(object value) =>
value == null
? throw new ArgumentNullException(nameof(value))
: Type.GetTypeCode(value.GetType()) != TypeCode.Object;
}
}
}