【问题标题】:Deserializing embedded array/list to array in c# as expandoobject将嵌入式数组/列表反序列化为 C# 中的数组作为 expandoobject
【发布时间】:2022-12-18 00:52:38
【问题描述】:

我有具有嵌入式数组的 JSON 对象 - 没有要反序列化的预定义强类型类。使用 Json.Net 的 ExpandoObject 反序列化有效,但数组被反序列化为列表,这对我来说是个问题。我需要带有数组的 expndoobject。是否有任何设置可以与 Json.NET 一起使用来实现此目的?

例子:

var obj = """
{
    "name": "John",
    "age": 18,
    "grid": [
        {
            "type": "A",
            "price": 13
        },
        {
            "type": "B",
            "price": 1
        },
        {
            "type": "A",
            "price": 17
        }
    ]
}
""";

var engine = new Engine()
   .Execute("function eval(value) { return value.grid.filter((it)=>it.type === 'A').map(it=>it.price).reduce((a,b)=>a+b) }");

dynamic v = JsonConvert.DeserializeObject<ExpandoObject>(obj, new ExpandoObjectConverter());

engine.Invoke("eval", v);

使用这个库的地方:https://github.com/sebastienros/jint 结果:

我在那里需要一个数组,否则调用将失败(“对象的属性‘过滤器’不是函数”)。

使用 dynamic v= Newtonsoft.Json.Linq.JObject.Parse(obj); 我得到了这个:

并且仍然失败:“访问的 JArray 值具有无效的键值:“过滤器”。预期为 Int32 数组索引。”

如果我为此示例定义类:

class Inner
{ 
    public string Type { get; set; }
    public int Price { get; set; }
}

class X
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Inner[] Grid { get; set; }
}

它被解析得很好(var v = JsonConvert.DeserializeObject&lt;X&gt;(obj);)并且代码返回我所期望的。当我使用 List&lt;Inner&gt; 而不是数组时,情况并非如此。因此问题是它不是一个数组。 所以我正在寻找在那个位置产生数组的任何解决方案。

【问题讨论】:

    标签: c# json json.net


    【解决方案1】:

    我创建了原始 ExpandoObjectConverter 的修改版本。那行得通。

    public static class _
    {
        public static bool MoveToContent(this JsonReader reader)
        {
            JsonToken tokenType = reader.TokenType;
            while (tokenType == JsonToken.None || tokenType == JsonToken.Comment)
            {
                if (!reader.Read())
                {
                    return false;
                }
                tokenType = reader.TokenType;
            }
            return true;
        }
    
        public static bool IsPrimitiveToken(this JsonToken token)
        {
            if ((uint)(token - 7) <= 5u || (uint)(token - 16) <= 1u)
            {
                return true;
            }
            return false;
        }
    }
    
    
    public class MyExpandoObjectConverter : JsonConverter
        {
            /// <summary>
            /// Writes the JSON representation of the object.
            /// </summary>
            /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
            /// <param name="value">The value.</param>
            /// <param name="serializer">The calling serializer.</param>
            public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
            {
                // can write is set to false
            }
    
            /// <summary>
            /// Reads the JSON representation of the object.
            /// </summary>
            /// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
            /// <param name="objectType">Type of the object.</param>
            /// <param name="existingValue">The existing value of object being read.</param>
            /// <param name="serializer">The calling serializer.</param>
            /// <returns>The object value.</returns>
            public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
            {
                return ReadValue(reader);
            }
    
            private object? ReadValue(JsonReader reader)
            {
                if (!reader.MoveToContent())
                {
                    throw new Exception("Unexpected end when reading ExpandoObject.");
                }
    
                switch (reader.TokenType)
                {
                    case JsonToken.StartObject:
                        return ReadObject(reader);
                    case JsonToken.StartArray:
                        return ReadList(reader);
                    default:
                        if (reader.TokenType.IsPrimitiveToken())
                        {
                            return reader.Value;
                        }
    
                        throw new Exception($"Unexpected token when converting ExpandoObject: {reader.TokenType}");
                }
            }
    
            private object ReadList(JsonReader reader)
            {
                IList<object?> list = new List<object?>();
    
                while (reader.Read())
                {
                    switch (reader.TokenType)
                    {
                        case JsonToken.Comment:
                            break;
                        default:
                            object? v = ReadValue(reader);
    
                            list.Add(v);
                            break;
                        case JsonToken.EndArray:
                            return list.ToArray();
                    }
                }
    
                throw new Exception("Unexpected end when reading ExpandoObject.");
            }
    
            private object ReadObject(JsonReader reader)
            {
                IDictionary<string, object?> expandoObject = new ExpandoObject();
    
                while (reader.Read())
                {
                    switch (reader.TokenType)
                    {
                        case JsonToken.PropertyName:
                            string propertyName = reader.Value!.ToString()!;
    
                            if (!reader.Read())
                            {
                                throw new Exception("Unexpected end when reading ExpandoObject.");
                            }
    
                            object? v = ReadValue(reader);
    
                            expandoObject[propertyName] = v;
                            break;
                        case JsonToken.Comment:
                            break;
                        case JsonToken.EndObject:
                            return expandoObject;
                    }
                }
    
                throw new Exception("Unexpected end when reading ExpandoObject.");
            }
    
            /// <summary>
            /// Determines whether this instance can convert the specified object type.
            /// </summary>
            /// <param name="objectType">Type of the object.</param>
            /// <returns>
            ///     <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
            /// </returns>
            public override bool CanConvert(Type objectType)
            {
                return (objectType == typeof(ExpandoObject));
            }
    
            /// <summary>
            /// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON.
            /// </summary>
            /// <value>
            ///     <c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>.
            /// </value>
            public override bool CanWrite => false;
        }
    
    ....
    dynamic v = JsonConvert.DeserializeObject<ExpandoObject>(obj, new MyExpandoObjectConverter());
    

    是的,我知道,JS 引擎部分可能会改变,因为这是测试版。但是,我的 JS 函数是完全有效的。我真的不希望引擎在与标准的兼容性方面变得更糟而不是更好。但我的错误与此无关。我只是要求一个反序列化为数组而不是列表的解决方案。

    【讨论】:

      【解决方案2】:

      为什么不尝试这样的事情

      Inner[] inners = JObject.Parse(obj).Properties()
                  .Where( p=> p.Value.Type== JTokenType.Array)
                  .SelectMany(p => p.Value.ToObject<Inner[]>())
                  .ToArray();
      

      【讨论】:

        【解决方案3】:

        这是一个 Jint 问题。最新的稳定版甚至不会解析 JS 函数。以下代码,使用最新的稳定版 2.11.58,抛出没有任何数据:

        using Jint;
        
        var js = """
            function eval(value) { 
                return value.grid.filter((it) => it.type === 'A')
                                 .map(it => it.price)
                                 .reduce((a,b)=>a+b) 
            }
            """;
        
        var engine = new Engine().Execute(js);
        

        这抛出

        Jint.Parser.ParserException
          HResult=0x80131500
          Message=Line 2: Unexpected token >
          Source=Jint
          StackTrace:
           at Jint.Parser.JavaScriptParser.ThrowError(Token token, String messageFormat, Object[] arguments)
           at Jint.Parser.JavaScriptParser.ThrowUnexpected(Token token)
           at Jint.Parser.JavaScriptParser.ParsePrimaryExpression()
           at Jint.Parser.JavaScriptParser.ParseLeftHandSideExpressionAllowCall()
        ...
        

        最新的稳定版一开始不理解箭头功能。

        最新的 3.0 beta,3.0.0.-3044 可以解析这个但是抛出一个不同的错误比问题中的错误。我想最新的测试版已经取得了一些进展。这次filter 被识别了,但是还没有用。

        使用数据调用函数

        var obj = """
        {
            "name": "John",
            "age": 18,
            "grid": [
                {
                    "type": "A",
                    "price": 13
                },
                {
                    "type": "B",
                    "price": 1
                },
                {
                    "type": "A",
                    "price": 17
                }
            ]
        }
        """;
        
        dynamic v = JsonConvert.DeserializeObject<dynamic>(obj);
        
        engine.Invoke("eval", v);
        

        投掷

        System.ArgumentException
          HResult=0x80070057
          Message=Accessed JArray values with invalid key value: "filter". Int32 array index expected.
          Source=Newtonsoft.Json
          StackTrace:
           at Newtonsoft.Json.Linq.JArray.get_Item(Object key)
           at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
           at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
        --- End of stack trace from previous location ---
           at Jint.Runtime.ExceptionHelper.ThrowMeaningfulException(Engine engine, TargetInvocationException exception)
           at Jint.Runtime.Interop.Reflection.ReflectionAccessor.GetValue(Engine engine, Object target)
           at Jint.Runtime.Descriptors.Specialized.ReflectionDescriptor.get_CustomValue()
        ...
        

        在这种情况下,Jint 尝试使用filter 作为grid 的索引值。

        数组访问虽然可以工作,但不是 JArray 导致了问题。

        这个 JS 函数:

        var js = """
            function eval(value) { 
                return value.grid[0].type 
            }
            """;
        

        工作和回报A

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多