3

I've been testing dynamically handling json on my backend without using objects (since I won't know the structure of the objects and it's a complete waste of my time creating them for the thousands of connections this will be making). I want to be able to program an interface without having to custom code every single rest interface I will be hooking up to and put the data in the hands of technical people but not programmers like myself.

In playing with NewtonSoft.Json I've come to realize that there are some oddities that I'm not quite understanding and I'm not alone as I've seen numerous people write similar questions with no resolution or understanding of what is going on.

I found numerous questions on here that referred to DeserializeObject as returning a JObject with an extra set of curly braces.

Example:

String "data" contains this:

{\r\n  \"firstName\": \"John\",\r\n  \"lastName\": \"Doe\",\r\n  \"IDNum\": 123456,\r\n  \"phoneNumbers\": [\r\n    {\r\n      \"number\": \"6123334444\"\r\n    },\r\n    {\r\n      \"number\": \"7635556666\"\r\n    }\r\n  ]\r\n}

JObject rest = JsonConvert.DeserializeObject<dynamic>(data.ToString()); 

But the JObject "rest" gets an extra set of curly braces added to it:

{{
    "firstName": "John",
    "lastName": "Doe",
    "IDNum": 123456,
    "phoneNumbers": [
    {
       "number": "6123334444"
    },
    {
      "number": "7635556666"
    }
  ]
}}

Making this JArray command not functional:

JArray obj = JArray.Parse(rest ) as JArray;

One of the posts on here referred to converting it back to a string and just replacing the curly braces like this:

string cleanRest = rest.ToString().Replace("{{", "{").Replace("}}", "}");

Which ends up with this in string "cleanRest":

{\r\n  \"firstName\": \"John\",\r\n  \"lastName\": \"Doe\",\r\n  \"IDNum\": 123456,\r\n  \"phoneNumbers\": null\r\n}

Of course cleanRest is not a valid json and that line throws the error: "Newtonsoft.Json.JsonReaderException: 'Error reading JArray from JsonReader. Current JsonReader item is not an array: StartObject. Path '', line 1, position 1.'" when running the following line:

JArray obj = JArray.Parse(cleanRest) as JArray;

I first had issues with the response including xmlns: http://schemas.microsoft.com/2003/10/Serialization and I had to open it as an XmlDocument and convert it with this:

    private string returnJson(string response)
    {
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(response);
        return doc.DocumentElement.FirstChild.InnerText;
    }

But all this noodling around is making me think I'm not dealing with this correctly even though I read a couple of blogs that referred to MS implementation of json has weird quirks like this. But I shouldn't have to mend the response; that's just making me nervous for future issues with this.

Does anyone know what is going on and how to solve it?

Zonus
  • 2,313
  • 2
  • 26
  • 48
  • JArray.parse takes a string, but `rest` is a dynamic (more like a dictionary in this case). Why would that work? – Garr Godfrey May 04 '20 at 23:24
  • Just drop the `` – JohanP May 04 '20 at 23:29
  • Using a dictionary I could creating a dynamic mapping that will map the external json object to my internal objects using .key and .value. I can't drop , I get a compiler error: "cannot implicitly convert type 'object' to Newtonsoft.Json.Linq.JObject" – Zonus May 04 '20 at 23:40
  • it's "like" a dictionary. It's a JObject. Since it isn't a string, saying it has a "{" at the beginning makes no sense. Maybe rewrite your question – Garr Godfrey May 04 '20 at 23:42
  • I'm copying and pasting the contents of the variables. I'm not going to modify them because what is happening is what my question is. – Zonus May 04 '20 at 23:44
  • well that doesn't work. The debugger adds to text it. So the extra { is just something the debugger is showing you, but doesn't exist if you serialize `rest` in code – Garr Godfrey May 04 '20 at 23:46
  • @DaBlue just cast to `JObject`, when you Deserialze using nongeneric version, the object is a `JObject`, there is no need to deserialze to `dynamic`, it is plenty wasteful. – JohanP May 05 '20 at 00:03
  • @JohanP I get the response wrapped in XML with this at the beginning of it: which makes it an invalid json structure. So I imported into XML with the function listed above to get the real json. Unless you have a better way? I searched for a couple of days to resolve that issue... – Zonus May 05 '20 at 00:16
  • @DaBlue I'm saying instead of this `JObject rest = JsonConvert.DeserializeObject(data.ToString()); ` do this `JObject rest = (JObject)JsonConvert.DeserializeObject(data.ToString()); ` – JohanP May 05 '20 at 00:19
  • @JohanP Thanks, that works, too. Although, I'm not sure why this is better than Deserializing to (pardon my ignorance on this I'm learning it...) – Zonus May 05 '20 at 00:23
  • 2
    `dynamic` is a hardcore type, you can read Eric Lippert's detailed explanation of what happens (https://stackoverflow.com/a/7480977/7484766) as well as how all that affects performance (https://dev.to/mokenyon/benchmarking-and-exploring-c-s-dynamic-keyword-1l5i) – JohanP May 05 '20 at 00:31

1 Answers1

1

So, parsing the JSON string doesn't give you another string you can parse. It creates an object. It needs to be serialized again. It works correctly.

However, the object represented by the string is not an array. It will give an error if you serialize it and pass to JArray.parse because it isn't an array.

You can actually serialize rest using ToString, but just passing rest to JArray.parse won't compile, so idk what you really are doing.

Here is some code that works

public static void Main()
{
    var json = "{\r\n  \"firstName\": \"John\",\r\n  \"lastName\": \"Doe\",\r\n  \"IDNum\": 123456,\r\n  \"phoneNumbers\": [\r\n    {\r\n      \"number\": \"6123334444\"\r\n    },\r\n    {\r\n      \"number\": \"7635556666\"\r\n    }\r\n  ]\r\n}";

    JObject rest = JsonConvert.DeserializeObject<dynamic>(json); 

    Console.WriteLine(rest.ToString());

    // this works, since phoneNumbers is an array
    JArray obj = JArray.Parse(rest["phoneNumbers"].ToString()) as JArray;       

    // this fails because an object is not an array
    obj = JArray.Parse(rest.ToString()) as JArray;      
}

If you want to access the properties, you can use the array syntax if you know the name, ie. rest["phoneNumbers"]. Or, if you don't know what properties it has you can do this:

foreach (var x in rest)
{
    string name = x.Key;
    JToken value = x.Value;
}

You can also change the values:

rest["firstName"] = "My New Name";

or add new ones

rest["newproperty"] = 2500;
Garr Godfrey
  • 8,257
  • 2
  • 25
  • 23
  • The variable above called "data" is coming from a rest service. I'm not giving a static string to the DeserializeObject... – Zonus May 04 '20 at 23:47
  • Ah, I think I understand what you are saying. Is there a way to convert this into a dictionary so I can reference it dynamically? – Zonus May 04 '20 at 23:48
  • That shouldn't make any difference unless your `data` string is corrupted in the first place. see this fiddle: https://dotnetfiddle.net/tcwxAb – Garr Godfrey May 04 '20 at 23:49
  • JObject is already accessible dynamically. You want to loop through the properties? – Garr Godfrey May 04 '20 at 23:49
  • Yes, I need to be able to set json service's firstName = myFirstName in my DB. – Zonus May 04 '20 at 23:53