将JSON字符串解析为.NET对象

5

我将使用Newtonsoft库将一个JSON字符串解析为相应的.NET对象。但是,当解析JSON属性为数组时遇到了问题。有时,JSON属性是一个数组,而其他时候则只是一个单独的元素。

例如:

这是.NET对象:

  public class xx
  {
      public string yy { get; set; }       
      public List<string> mm{ get; set; }        
  }

当我收到这个JSON时:
{ "xx": {"yy":"nn", "mm": [ "zzz", "aaa" ] } }

我可以完美地做到以下几点:

JsonConvert.DeserializeObject<xx>(json);

但是有时我会收到这个 JSON:


{ "xx": {"yy":"nn", "mm":"zzz"} }

由于C#对象上的列表属性,反序列化失败。

我该如何定义一个对象来反序列化两个JSON字符串到同一个对象中(使用 List<string>)?

-------- 更新 -----

首先,WS生成一个执行某些操作的XML。 XML类似于:

<xx yy='nn'><mm>zzz</mm></xx>

如果有更多的元素,则为:

<xx yy='nn'><mm>zzz</mm><mm>aaa</mm></xx>

最终,WS将执行以下操作转换此XML:
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);           
var json = JsonConvert.SerializeXmlNode(doc); 

并将 JSON 发送给我...这里开始出现了我的问题...


2
你不能改变JSON生成的方式吗?因为这种方式相当不一致,总是生成一个数组更有意义。 - svick
1
为什么传入的JSON对象格式不一致?如果mm可能包含多个元素,则应始终将其表示为数组([]),而不是简单的name: value对。 - Kirk Woll
在将JSON发送到服务器之前,您应该能够对其进行处理,使其始终采用有效的格式。展示您用于构建JSON对象的JS代码。 - arb
好的,我可能会更改JSON... 我从WS接收到这个JSON。在这个WS中有一些操作,最后生成一个XML。这个XML有时是<xx yy='nn'><mm>zzz</mm></xx>,有时是<xx yy='nn'><mm>zzz</mm><mm>aaa</mm></xx>,取决于元素的数量... 最后,该WS将此XML转换为JSON(使用JsonConvert.SerializeXmlNode(doc)),并将其发送给我,这就是我的问题开始的地方... - Vic Naranja
@VicNaranja,你为什么要将XML转换为JSON?为什么不直接使用XML? - svick
Svick,你知道的,这是一个摘要。在我们的WS中,我们通过其他WS获取XML数据,然后进行一些操作,最后返回一个JSON对象(这是一个要求)。因此,我正在寻找将XML转换为JSON并始终获得相同结果(数组)的最佳方法。通常我会将XML转换为.Net对象,在这种情况下,无论有一个元素还是多个元素,都会返回一个列表,没有问题。 - Vic Naranja
4个回答

5
更新的回答:
查看JSON.Net如何映射XML,它采取的方法是看到什么就序列化什么,但如果看到多个相同节点,则会创建一个数组。这对于许多具有一致布局的XML DOM树非常有用,但不幸的是不能满足您的需求。
您可以通过查看以下文件源中SerializeGroupedNodes()SerializeNode()函数的主体来验证此内容。 XmlNodeConverter.cs source code @ CodePlex, ChangeSet #63616 还有另一种选择,我之前认为可能过度了,但现在我们知道如何从序列化端默认行为中获得帮助,这将非常有用。
Json.Net支持使用派生自JsonConverter的自定义转换器,以基于情况按案例映射特定值。
我们可以在序列化端或反序列化端处理此问题。我选择在反序列化端编写解决方案,因为您可能已经有其他现有原因将XML映射到JSON。
一个很大的好处是你的类可以保持完整,只需要应用属性来覆盖。下面是一个代码示例,演示如何使用 JsonAttribute 和自定义转换器类(MMArrayConverter)来解决你的问题。请注意,你可能需要更加彻底地测试它,并且也许需要更新转换器以处理其他情况,比如如果你最终迁移到 IList<string> 或一些其他奇怪的情况,或者甚至使它与泛型一起工作。
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;

namespace JsonArrayImplictConvertTest
{
    public class MMArrayConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType.Equals(typeof(List<string>));
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.StartArray)
            {
                List<string> parseList = new List<string>();
                do
                {
                    if (reader.Read())
                    {
                        if (reader.TokenType == JsonToken.String)
                        {
                            parseList.Add((string)reader.Value);
                        }
                        else
                        {
                            if (reader.TokenType == JsonToken.Null)
                            {
                                parseList.Add(null);
                            }
                            else
                            {
                                if (reader.TokenType != JsonToken.EndArray)
                                {
                                    throw new ArgumentException(string.Format("Expected String/Null, Found JSON Token Type {0} instead", reader.TokenType.ToString()));
                                }
                            }
                        }
                    }
                    else
                    {
                        throw new InvalidOperationException("Broken JSON Input Detected");
                    }
                }
                while (reader.TokenType != JsonToken.EndArray);

                return parseList;
            }

            if (reader.TokenType == JsonToken.Null)
            {
                // TODO: You need to decide here if we want to return an empty list, or null.
                return null;
            }

            if (reader.TokenType == JsonToken.String)
            {
                List<string> singleList = new List<string>();
                singleList.Add((string)reader.Value);
                return singleList;
            }

            throw new InvalidOperationException("Unhandled case for MMArrayConverter. Check to see if this converter has been applied to the wrong serialization type.");
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            // Not implemented for brevity, but you could add this if needed.
            throw new NotImplementedException();
        }
    }

    public class ModifiedXX
    {
        public string yy { get; set; }

        [JsonConverter(typeof(MMArrayConverter))]
        public List<string> mm { get; set; }

        public void Display()
        {
            Console.WriteLine("yy is {0}", this.yy);
            if (null == mm)
            {
                Console.WriteLine("mm is null");
            }
            else
            {
                Console.WriteLine("mm contains these items:");
                mm.ForEach((item) => { Console.WriteLine("  {0}", item); });
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string jsonTest1 = "{\"yy\":\"nn\", \"mm\": [ \"zzz\", \"aaa\" ] }";
            ModifiedXX obj1 = JsonConvert.DeserializeObject<ModifiedXX>(jsonTest1);
            obj1.Display();

            string jsonTest2 = "{\"yy\":\"nn\", \"mm\": \"zzz\" }";
            ModifiedXX obj2 = JsonConvert.DeserializeObject<ModifiedXX>(jsonTest2);
            obj2.Display();

            // This test is now required in case we messed up the parser state in our converter.
            string jsonTest3 = "[{\"yy\":\"nn\", \"mm\": [ \"zzz\", \"aaa\" ] },{\"yy\":\"nn\", \"mm\": \"zzz\" }]";
            List<ModifiedXX> obj3 = JsonConvert.DeserializeObject<List<ModifiedXX>>(jsonTest3);
            obj3.ForEach((obj) => { obj.Display(); });

            Console.ReadKey();
        }
    }
}

原始回答:

最好在源头修复你收到的 JSON,正如许多人已经指出的那样。你可能希望发布一个更新,展示你如何将你更新的评论中的 XML 映射到 JSON,因为这将是最好的路线。

然而,如果你发现这不可能,并且想要一些方法来序列化和处理变量值,那么你可以声明 mm 为类型 object,然后使用 JSON.Net 的 Linq 支持自己处理可能的情况。在你描述的两种情况中,你会发现将 mm 声明为类型 object 将导致调用 DeserializeObject<> 时将 nullstringJArray 分配给 mm

以下是一个代码示例,展示了这一操作的实现方法。在其他情况下,您可能会收到一个JObject,这也在此示例中进行了说明。请注意,成员函数mmAsList()将为您修补差异。还请注意,我通过返回null来处理null,对于List<string>,您可能需要根据实现情况进行修改。
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace JsonArrayUnionTest
{
    public class ModifiedXX
    {
        public string yy { get; set; }
        public object mm { get; set; }

        public List<string> mmAsList()
        {
            if (null == mm) { return null; }
            if (mm is JArray)
            {
                JArray mmArray = (JArray)mm;
                return mmArray.Values<string>().ToList();
            }

            if (mm is JObject)
            {
                JObject mmObj = (JObject)mm;
                if (mmObj.Type == JTokenType.String)
                {
                    return MakeList(mmObj.Value<string>());
                }
            }

            if (mm is string)
            {
                return MakeList((string)mm);
            }

            throw new ArgumentOutOfRangeException("unhandled case for serialized value for mm (cannot be converted to List<string>)");
        }

        protected List<string> MakeList(string src)
        {
            List<string> newList = new List<string>();
            newList.Add(src);
            return newList;
        }

        public void Display()
        {
            Console.WriteLine("yy is {0}", this.yy);
            List<string> mmItems = mmAsList();
            if (null == mmItems)
            {
                Console.WriteLine("mm is null");
            }
            else
            {
                Console.WriteLine("mm contains these items:");
                mmItems.ForEach((item) => { Console.WriteLine("  {0}", item); });
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string jsonTest1 = "{\"yy\":\"nn\", \"mm\": [ \"zzz\", \"aaa\" ] }";
            ModifiedXX obj1 = JsonConvert.DeserializeObject<ModifiedXX>(jsonTest1);
            obj1.Display();

            string jsonTest2 = "{\"yy\":\"nn\", \"mm\": \"zzz\" }";
            ModifiedXX obj2 = JsonConvert.DeserializeObject<ModifiedXX>(jsonTest2);
            obj2.Display();

            Console.ReadKey();
        }
    }
}

1
发送服务发送的内容应该符合合同。如果不符合,那么你要么找发送方开发人员解决问题,要么接受发送给你的各种内容就是合同。可惜你没有任何元数据可以确定,只能尝试多种合同直到找到一个可行的。
object someValue;
try
{
   someValue =JsonConvert.DeserializeObject<TypeWithList>(json);
}
catch
{
    try
    {
      someValue = JsonConvert.DeserializeObject<TypeWithString>(json);
    }
    catch
    {
    //Darn, yet another type
    }
}

哎呀,如果事情是这样的话,那就太可怕了。我没有想到他可能无法控制自己接收到的内容。 - arb
我曾想过这种解决方案,但我一点也不喜欢。我想也许通过某些属性可以解决这个问题。 - Vic Naranja

0
在您的情况下,您可以直接使用JsonConvert类中的静态方法:

PopulateObject(string value, object target, JsonSerializerSettings settings);

将JsonSerializerSettings对象作为参数传递。
new JsonSerializerSettings(){TypeNameHandling = TypeNameHandling.All})

-2

我认为你需要查看你的Javascript对象。如果你明确声明要序列化为JSON的对象属性的类型,那么就不会遇到任何不一致的情况。

var stringProperty = new String();
var arrayProperty = new Array();

// Assign value to stringProperty and push elements into arrayProperty

var object = {
    stringProperty: stringProperty,
    arrayProperty: arrayProperty
};

var jsonObject = JSON.stringify(object);

document.write(jsonObject);

如果您查看输出,您会发现arrayProperty始终被序列化为一个数组,无论有零个、一个或多个元素。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接