json 将索引属性反序列化为数组

7gs2gvoe  于 2023-06-07  发布在  其他
关注(0)|答案(4)|浏览(347)

假设我有一些JSON,它将以这样的包形式出现:

{
    "LinkType1": "google",
    "LinkUrl1": "https://plus.google.com/test",
    "LinkShow1": 1,

    "LinkType2": "facebook",
    "LinkUrl2": "https://www.facebook.com/test",
    "LinkShow2": 0,

    "LinkType3": "linkedin",
    "LinkUrl3": "http://www.linkedin.com/test",
    "LinkShow3": 1,

    "count": 3,
    "errorCode": 0,
    "errorMessage": "Success"
}

注意到所有内容都是以相同的属性返回的,但带有索引吗?
我希望能够反序列化数据,就好像它是一个数组而不是单个属性。将其反序列化到下面的类中的最佳方法是什么?我使用Newtonsoft Json库进行序列化,因此使用它的解决方案将是首选。

public class LinksResult
    {
        public List<LinkData> Links { get; set; } 

        [JsonProperty("count")]
        public int Count { get; set; }

        [JsonProperty("errorCode")]
        public int ErrorCode { get; set; }

        [JsonProperty("errorMessage")]
        public string ErrorMessage { get; set; }
    }

    public class LinkData
    {
        public string LinkType { get; set; }
        public string LinkUrl { get; set; }
        public bool LinkShow { get; set; }
    }
qnakjoqk

qnakjoqk1#

您可以使用自定义JsonConverter将JSON数据反序列化为您想要的结构。下面是转换器的代码。

class LinksResultConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(LinksResult));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject obj = JObject.Load(reader);
        LinksResult result = new LinksResult();
        result.Count = (int)obj["count"];
        result.ErrorCode = (int)obj["errorCode"];
        result.ErrorMessage = (string)obj["errorMessage"];
        result.Links = new List<LinkData>();

        for (int i = 1; i <= result.Count; i++)
        {
            string index = i.ToString();
            LinkData link = new LinkData();
            link.LinkType = (string)obj["LinkType" + index];
            link.LinkUrl = (string)obj["LinkUrl" + index];
            link.LinkShow = (int)obj["LinkShow" + index] == 1;
            result.Links.Add(link);
        }

        return result;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

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

要使用转换器,只需将[JsonConverter]属性添加到LinksResult类,如下所示。(注意,这种方法不需要[JsonProperty]属性,因为JSON属性名和实际类成员之间的Map由转换器直接处理。

[JsonConverter(typeof(LinksResultConverter))]
public class LinksResult
{
    public List<LinkData> Links { get; set; }
    public int Count { get; set; }
    public int ErrorCode { get; set; }
    public string ErrorMessage { get; set; }
}

然后,您可以像这样反序列化:

LinksResult result = JsonConvert.DeserializeObject<LinksResult>(json);

小提琴:https://dotnetfiddle.net/56b34H

neskvpey

neskvpey2#

布莱恩的回答非常好,它让我80%的方式,我想成为。但是,如果这种模式在许多不同的对象上发生,那么它就不是一个很好的实现。
我做了一些更通用的东西。一个“页面”应该有的界面。

public interface IPage<TItem>
{
    int Count { get; set; }
    List<TItem> PageItems { get; set; }
}

然后是页面转换器本身。

public class PageConverter<TPage, TItem> : JsonConverter
        where TPage : IPage<TItem>, new()
        where TItem : new()
{
    private readonly Regex _numberPostfixRegex = new Regex(@"\d+$");

    public override bool CanWrite
    {
        get { return false; }
    }

    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(TPage));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var obj = serializer.Deserialize<JObject>(reader);
        var page = new TPage();
        serializer.Populate(obj.CreateReader(), page); //Loads everything that isn't a part of the items. 
        page.PageItems = new List<TItem>();

        for (int i = 1; i <= page.Count; i++)
        {
            string index = i.ToString();

            //Find all properties that have a number at the end, then any of those that are the same number as the current index.
            //Put those in a new JObject.
            var jsonItem = new JObject();
            foreach (var prop in obj.Properties().Where(p => _numberPostfixRegex.Match(p.Name).Value == index))
            {
                jsonItem[_numberPostfixRegex.Replace(prop.Name, "")] = prop.Value;
            }

            //Deserialize and add to the list.
            TItem item = jsonItem.ToObject<TItem>(serializer);
            page.PageItems.Add(item);
        }

        return page;
    }

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

所以接下来需要做的就是在链接结果上实现它:

[JsonConverter(typeof(PageConverter<LinksResult, LinkData>))]
public class LinksResult : IPage<LinkData>
{
    public int Count { get; set; }

    public List<LinkData> PageItems { get; set; }
}

我发现你可以control the serialization of capitalization with JsonSerializerSettings,所以最好把细节留给选择的串行器,而不是我的转换器。
Fiddle在这里:https://dotnetfiddle.net/7KhwYY

xurqigkl

xurqigkl3#

这里是类似的解决方案,您可以申请。请参阅Serialize json to an object with catch all dictionary property大卫Hoerster的答案。

myss37ts

myss37ts4#

下面是我提出的一个解决方案,效果很好。
有一些类似下面的JSON示例。JSON非常扁平,您可以看到索引的检查/传输属性。我想让NewtonSoft尽可能多地完成繁重的工作。

{
  "id": "209348",
  "Check__00__amount": 10000,
  "Check__00__payableTo": "ABC Company",
  "Check__00__receivedFrom": "Mike",
  "Check__01__amount": 20000,
  "Check__01__payableTo": "XYZ Company",
  "Check__01__receivedFrom": "Jim",
  "Transfer00__Amount": 50000.0,
  "Transfer00__CompanyTransferringFrom": "DEF Company",
  "Transfer00__Type": "Partial",
  "Transfer01__Amount": 55000.0,
  "Transfer01__CompanyTransferringFrom": "GHI Company",
  "Transfer01__Type": "Full"
}

要反序列化到的类型。顶级对象Transaction,具有用于检查和传输的两个列表属性。我使用的是自定义转换器和自定义属性。

[JsonConverter(typeof(TestConverter))]
public class Transaction
{
    [JsonProperty("id")]
    public int Id { get; set; } = default!;
    
    [RegexIndexedPropertiesToList(@"Transfer\d+__")]
    public List<Transfer> Transfers { get; set; } = default!;
    
    [RegexIndexedPropertiesToList(@"Check__\d+__")]
    public List<Check> Checks { get; set; } = default!;
}

public class Check
{
    
    [JsonProperty("amount")]
    public decimal Amount { get; set; }
    [JsonProperty("payableTo")]
    public string PayableTo { get; set; } = default!;
    [JsonProperty("receivedFrom")]
    public string ReceivedFrom { get; set; } = default!;
    
}

public class Transfer
{
    [JsonProperty("Amount")]
    public decimal Amount { get; set; }
    [JsonProperty("CompanyTransferringFrom")]
    public string CompanyTransferringFrom { get; set; } = default!;
    [JsonProperty("Type")]
    public string Type { get; set; } = default!;
}

自定义属性。允许设置正则表达式。这个正则表达式应该能够匹配你想要转换成列表的属性。您可以在Transaction类的Checks/List属性上看到它的装饰。

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class RegexIndexedPropertiesToListAttribute : Attribute
{
    public string Regex { get; }
    public RegexIndexedPropertiesToListAttribute(string regex)
    {
        Regex = regex;
    }
}

该转换器

public class TestConverter : JsonConverter
{

    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)
    {
        var obj = JObject.Load(reader);
            
        //get a new instance and populate it to prevent infinite recursion on the converter, this will allow default serializer behavior to work
        var returnable = Activator.CreateInstance(objectType) ?? throw new InvalidOperationException("could not create instance");
        JsonConvert.PopulateObject(obj.ToString(), returnable); //use regular deserialization

        IndexedPropertiesToList(returnable, obj);

        return returnable;
    }

    

    private static void IndexedPropertiesToList(object returnable, JObject obj)
    {
        //get all the index to list properties
        var propsForConversion = returnable.GetType().GetProperties()
            .Where(x => x.IsDefined(typeof(RegexIndexedPropertiesToListAttribute), false));

        foreach (var prop in propsForConversion)
        {
            var attribute = (prop.GetCustomAttributes(typeof(RegexIndexedPropertiesToListAttribute), false).FirstOrDefault() as RegexIndexedPropertiesToListAttribute)
                            ?? throw new Exception("attribute not found");

            //assume the prperty to be set is a list
            var list = Activator.CreateInstance(prop.PropertyType) as IList ??
                       throw new InvalidOperationException("could not create instance");
            
            var regex = new Regex(attribute.Regex);

            //get the properties that match the regex and then group them using the regex as a prefix
            var matchedProperties = obj.Properties().Where(x => regex.IsMatch(x.Path)).ToList();
            var groups = matchedProperties.GroupBy(x => regex.Match(x.Path).Value).ToList();

            foreach (var group in groups)
            {
                var newObj = new JObject(); //create a new jobject will use this to deserialize the properties into type so that normal deserialization works
                foreach (var property in group)
                {
                    var name = property.Name.Replace(group.Key, "");
                    newObj.Add(name, property.Value); //add the property to the new object with no index
                }

                //assumes the List is of a generic type
                var genericType = prop.PropertyType.GenericTypeArguments[0];
                
                var instance = newObj.ToObject(genericType) ??
                               throw new InvalidOperationException("could not deserialize");
                list.Add(instance);
            }

            //set the constructed list of deserialized objects to the property
            prop.SetValue(returnable, list);
        }
    }

    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }
}

首先,转换器填充顶层(在本例中为事务)。然后查找用该属性修饰的属性。它使用该正则表达式匹配JSON中的字段,然后按匹配部分分组,Check__00__和Check__01__将是两个分组,每个分组有3个JToken用于相应的属性。从那里,它构建一个新的JObject,其中包含分组的字段。JObject将删除名称中与正则表达式匹配的部分,这允许使用JsonProperty属性进行Map。然后,它使用默认的Newtonsoft反序列化来获取List类型的新示例并将其添加到列表中,然后在顶级示例上设置List属性。
它工作得很好,为我所需要的。

使用常规NewtonSoft反序列化只需要自定义转换器和属性似乎在一些相当大的有效负载上执行得相当快。
我上面所做的假设是处理一个列表,如果不是这样的话,就需要进一步的调整。它将无法处理更深层次的嵌套,尽管我认为如果需要的话可以添加它,并且可能无法很好地处理原语列表,尽管我认为可以通过一些额外的代码来处理。

相关问题