【问题标题】:Deserializing a JSON array with awkward additional property in the schema反序列化模式中带有尴尬附加属性的 JSON 数组
【发布时间】:2021-07-17 04:37:29
【问题描述】:

我有一个与这个问题here 非常相似的问题,除了我的应用程序是用 C# 编写的,不幸的是我不知道如何转换解决方案。我正在尝试反序列化如下所示的 JSON 结果:

"error":[],
"result":
     {
        "MANAEUR":[
                [1619042400,"1.11200","1.13488","1.08341","1.10077","1.09896","58878.56534370",137],
                [1619046000,"1.09767","1.12276","1.08490","1.11097","1.10456","25343.25910419",77],

            ],
        
        "last":1619118000
    }

我使用以下类:

public class ResponseBase
{
    [JsonProperty(PropertyName = "error")]
    public List<string> Error;
}

public class OHLCResponse : ResponseBase
{
    [JsonProperty("result")]
    public OHLCResult Result;
}

public class OHLCResult
{
    [JsonProperty("pair_names")]
    public Dictionary<string, OHLC[]> GetHistory;

    [JsonProperty("last")]
    public long Last;
}

....最后是它的胆量:

public class OHLC
{
    public int Time;
    public decimal Open;
    public decimal High;
    public decimal Low;
    public decimal Close;
    public decimal Vwap;
    public decimal Volume;
    public int Count;
}

我有一个标准的反序列化器类,它适用于我对同一 API 使用的所有其他调用,但我无法让这个调用工作。当我检索 OHLCResponse 对象时,我没有收到错误,并且始终填充“Result.Last”,但“Result.GetHistory”中预期的 OHLC 项数组始终为空/null。我知道数据已成功返回,因为我可以看到从 WebRequest 返回的变量中的数据,然后我将其传递给反序列化器函数,所以我猜这些类的布局一定是错误的。

谁能看出我做错了什么?

非常感谢,戴夫

【问题讨论】:

  • 1) JSON 数据显示MANAEUR,但您的模型中有pair_names。 2) MANAEUR 是一个数组数组。不是数组字典。 3)你有OHLC作为一个对象,但它不是一个对象。这是一个数组;有几种方法可以使这项工作。您可以将MANAEUR 属性更改为List&lt;List&lt;object&gt;&gt;,然后对OHLC 对象执行.Select(),或者您可以使用自定义JsonConverter
  • 感谢您的 cmets 安迪。
  • 我将“OHLCResult”中的属性标签注释掉,然后将属性更改为“public List> GetHistory;”,但这并没有改变结果。不过,我很感谢您的意见。我不明白你所说的“.Select()”是什么意思。
  • 我假设您使用的是 Newtonsoft.Json?不是 System.Text.Json?
  • 是的。我在模型类中声明了“使用 Newtonsoft.Json”。

标签: c# json rest serialization kraken.com


【解决方案1】:

您发布的对象不是有效的 JSON。缺少外部花括号。所以我假设它应该是这样的:

{
    "error": [],
    "result": {
        "MANAEUR": [
            [1619042400, "1.11200", "1.13488", "1.08341", "1.10077", "1.09896", "58878.56534370", 137],
            [1619046000, "1.09767", "1.12276", "1.08490", "1.11097", "1.10456", "25343.25910419", 77],

        ],
        "last": 1619118000
    }
}

匿名反序列化

您可以做的第一种方法是使用匿名反序列化,这可能有点笨拙,因为您必须反序列化两次。

让我们从定义一些模型开始:

public sealed class OHLCModel
{
    public long Time { get; set; }
    public decimal Open { get; set; }
    public decimal High { get; set; }
    public decimal Low { get; set; }
    public decimal Close { get; set; }
    public decimal Vwap { get; set; }
    public decimal Volume { get; set; }
    public int Count { get; set; }
}

public sealed class ResultModel
{
    [JsonIgnore]
    public IEnumerable<OHLCModel> Manaeur { get; set; }

    [JsonProperty("last")]
    public long Last { get; set; }
}

public sealed class RootModel
{
    [JsonProperty("error")]
    public List<string> Error { get; set; }

    [JsonProperty("result")]
    public ResultModel Result { get; set; }
}

如您所见,当序列化发生时,我们忽略了 Manaeur 对象。

为了使这个方法有效,我们会这样做:

var json = System.IO.File.ReadAllText(@"c:\users\andy\desktop\test.json");

// First, just grab the object that has the mixed arrays.
// This creates a "template" of the format of the target object
var dto = JsonConvert.DeserializeAnonymousType(json, new
{
    Result = new
    {
        Manaeur = new List<List<object>>()
    }
});
            
// Next, deserialize the rest of it
var fullObject = JsonConvert.DeserializeObject<RootModel>(json);

// transfer the DTO using a Select statement
fullObject.Result.Manaeur = dto.Result.Manaeur.Select(x => new OHLCModel
{
    Time = Convert.ToInt64(x[0]),
    Open = Convert.ToDecimal(x[1]),
    High = Convert.ToDecimal(x[2]),
    Low = Convert.ToDecimal(x[3]),
    Close = Convert.ToDecimal(x[4]),
    Vwap = Convert.ToDecimal(x[5]),
    Volume = Convert.ToDecimal(x[6]),
    Count = Convert.ToInt32(x[7])
});

这不是最理想的解决方案,因为您在某些地方与模型紧密耦合。实现此目的的理想方法是自定义 JsonSerializer

使用自定义 JsonConverter

我们要做的第一件事是将您的 ResultModel 更改为如下所示:

public sealed class ResultModel
{
    [JsonConverter(typeof(ManaeurJsonConverter)), JsonProperty("MANAEUR")]
    public IEnumerable<OHLCModel> Manaeur { get; set; }

    [JsonProperty("last")]
    public long Last { get; set; }
}

然后实现一个JsonConverter:

public sealed class ManaeurJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) => false; // this will never get called

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var lst = JArray.Load(reader).ToObject<List<List<object>>>();
        return lst.Select(x => new OHLCModel
        {
            Time = Convert.ToInt64(x[0]),
            Open = Convert.ToDecimal(x[1]),
            High = Convert.ToDecimal(x[2]),
            Low = Convert.ToDecimal(x[3]),
            Close = Convert.ToDecimal(x[4]),
            Vwap = Convert.ToDecimal(x[5]),
            Volume = Convert.ToDecimal(x[6]),
            Count = Convert.ToInt32(x[7])
        });
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    { // we don't need to write
        throw new NotImplementedException();
    }
}

然后你可以简单地这样称呼它:

var json = System.IO.File.ReadAllText(@"c:\users\andy\desktop\test.json");

var fullObject = JsonConvert.DeserializeObject<RootModel>(json);

【讨论】:

  • 太好了,非常感谢您抽出宝贵的时间来写下所有内容。我会在早上的第一件事尝试。非常感谢。
  • 你好安迪,第一个解决方案给了我数据,非常感谢。第二种解决方案不起作用,因为 JSON 属性是动态的: JsonProperty("MANAEUR") ......... "MANAEUR" 不是静态的。非常感谢您抽出宝贵时间,我已将此标记为已回答。
  • 我很快就谈过了。第一个建议在仔细检查后也不起作用,因为不幸的是“MANAEUR”是可变的。该死的。
  • @DavyC - 你的问题没有说明这一点。你甚至发布了OHLC 模型。这有点令人沮丧。那时你唯一的选择就是选择另一个答案。
  • 你好,安迪。是的,为此道歉,我想我做了一个假设。非常非常感谢您的帮助。
【解决方案2】:

您的 JSON 无效,我必须对其进行修改以使其成为有效的 JSON (https://jsonformatter.org/)。我在第二个内部数组条目之后添加了根括号并删除了逗号分隔符。

有效的 JSON:

{
  "error":[],
  "result":
     {
        "MANAEUR":[
                [1619042400,"1.11200","1.13488","1.08341","1.10077","1.09896","58878.56534370",137],
                [1619046000,"1.09767","1.12276","1.08490","1.11097","1.10456","25343.25910419",77]
            ],
        
        "last":1619118000
    }
}

更新 JSON 后,我使用 Visual Studio 的“Paste Special”从 JSON 生成 C# 对象。创建了以下类。

public class RootObject
{
    [JsonProperty("error")]
    public object[] Error { get; set; }

    [JsonProperty("result")]
    public Result Result { get; set; }
}

public class Result
{
    [JsonProperty("MANAEUR")]
    public object[][] Manaeur { get; set; }

    [JsonProperty("last")]
    public int Last { get; set; }
}

使用上面的 JSON 和类,我使用以下内容对 JSON 进行反序列化。

string json = "{\"error\":[],\"result\":{\"MANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
var obj = JsonConvert.DeserializeObject<RootObject>(json);

编辑: 处理键标签可以不同的MANAEUR 属性。

创建一个 JsonConverter...

public class ManaeurConverter : JsonConverter
{
    private Dictionary<string, string> propertyMappings { get; set; }
       
    public ManaeurConverter()
    {
        this.propertyMappings = new Dictionary<string, string>
        {
            {"NOTMANAEUR","MANAEUR"}
        };
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        object instance = Activator.CreateInstance(objectType);
        var props = objectType.GetTypeInfo().DeclaredProperties.ToList();

        JObject jo = JObject.Load(reader);
        foreach (JProperty jp in jo.Properties())
        {
            if (!propertyMappings.TryGetValue(jp.Name, out var name))
                name = jp.Name;

            PropertyInfo prop = props.FirstOrDefault(pi =>
                    pi.CanWrite && pi.GetCustomAttribute<JsonPropertyAttribute>().PropertyName == name);

        prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
        }
        return instance;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType.GetTypeInfo().IsClass;
    }

    public override bool CanWrite => false;
}

...在类中添加JsonConverter属性...

[JsonConverter(typeof(ManaeurConverter))]
public class Result
{
    [JsonProperty("MANAEUR")]
    public object[][] Manaeur { get; set; }

    [JsonProperty("last")]
    public int Last { get; set; }
}

...然后像这样解析...

string json_Manaeur = "{\"error\":[],\"result\":{\"MANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
string json_Not_Manaeur = "{\"error\":[],\"result\":{\"NOTMANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
            
var objManaeur = JsonConvert.DeserializeObject<RootObject>(json_Manaeur);
var objNotManaeur = JsonConvert.DeserializeObject<RootObject>(json_Not_Manaeur);

【讨论】:

  • 太好了。非常感谢。抱歉,当我发布我的 JSON 代码时,我一定是砍掉了 { } 字符。这个解决方案很有效,但“MANAEUR”字段是动态的,并且标签会发生变化。我不能明确地将其标记为 JsonProperty。你知道有没有办法解决这个问题?非常非常感谢您迄今为止的帮助。
  • 这不会让您具体访问数组的值。我认为这就是你所需要的。
  • 原来这是唯一正确的答案。 OP 没有提到 MANAEUR 对象每次都不同。
  • @DavyC 我已经更新了我的答案以解决MANAEUR 标签可能不同的问题。
  • 这太棒了!太感谢了。我希望我能赞成这个作为答案。就一件事。你知道我如何解耦这部分,以便我可以将预期的 PropertyName 作为变量传递吗?我会知道运行时的预期值应该是什么,所以字典键需要是动态的:{"NOTMANAEUR","MANAEUR"}
猜你喜欢
  • 2021-11-12
  • 1970-01-01
  • 2020-03-30
  • 1970-01-01
  • 2021-05-03
  • 2017-07-18
  • 2021-07-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多