-3

I have a JSON string that contains an object that is complex, nested, and will likely change schema in the future. I would like to hand it off to a web API to serialize in the content negotiation pipeline as needed.

Is there any straightforward way of doing this? I've read other answers on SO, but they only discuss either cases where you know the schema you want beforehand (eg deserialize with JsonConvert.DeserializeAnonymousType) or when you know the nesting depth that you want to deserialize to.

So for example let's say I have the following string:

    @"{
      name: "Dan"
      children: [
         {
            name: 'Fred',
         },
         {
            name: 'Fannie',
            age: 30,
            children: {
                own: [
                    {name: "Barney"},
                    {name: "Angela"}
                ],
                adopted: {
                    {name: "Sven"}
                }            
            }  
        }
    }"

I don't know what the schema of it is, and it can change at any time, I just want to be able to send it via web api with proper content negotiation.

I can do JObject.Parse(...) but web api can't handle JObjects properly. It would handle dictionaries properly, but I can't seem to figure out how to use JSON.Net to deserialize dictionaries of arbitrary nesting depth.

George Mauer
  • 117,483
  • 131
  • 382
  • 612
  • 1
    Did you just say you want to serialize a JSON string as JSON? – Nick Aug 29 '14 at 20:09
  • You can always use `JObject.Parse(json)` which returns a parsed JObject (which also implements IDictonary).But I don't understand how you want to get the properties out of it without knowing anything about it. – L.B Aug 29 '14 at 20:13
  • @L.B. I want to send it down to the client using web api. Web Api can serialize it as xml or json or something else depending on available handlers. However, it can't go to xml from `JObject` – George Mauer Aug 29 '14 at 20:14
  • @George Mauer - OK, but your question says "I have a JSON string...I would like to hand it off to a web API to serialize as XML or JSON" Anyway, how would you even deal with such an object? If you don't know it's schema, you don't know its properties. Are you just interacting with it entirely through reflection? – Nick Aug 29 '14 at 20:15
  • @Nick I'm interacting with it on the client side. This is no different from what people who use Couch or Riak do when they talk directly to the database webserver, I just want the content negotiation to be handled by WebApi. Web api would work just fine with nested dictionaries or anonymous types or dynamic type providers so yes, on some level WebApi at least is using reflection. – George Mauer Aug 29 '14 at 20:18
  • Do you have to support XML? If not, you can remove that formatter and then use a JObject with a method signature of Object. – John Koerner Aug 29 '14 at 20:20
  • Yes, I would like to support xml from this. Ideally if someone types the url directly into their browser it should return it in xml rather than json – George Mauer Aug 29 '14 at 20:21
  • `Dictionary` is not serializable. You can implement your own serializable dictionary, and someone else has already done it: http://weblogs.asp.net/pwelter34/444961 Using this could you just nest SerializableDictionary objects inside the value of a SerializableDictionary? – Nick Aug 29 '14 at 20:29
  • @Nick `Dictionary` **is serializable** with Json.Net – L.B Aug 29 '14 at 20:32
  • @GeorgeMauer is your question "how can I convert JObject to XElement", in short? – L.B Aug 29 '14 at 20:34
  • No...it's how to convert a json string into something that is serializable by WebApi. I should change the topic... – George Mauer Aug 29 '14 at 20:36
  • 1
    @GeorgeMauer Then why not just convert the JSON to an object, then serialize the object as necessary to JSON to XML? I think you're confusing everyone on what you're trying to accomplish because you haven't described the problem concisely. – mason Aug 29 '14 at 20:41
  • @mason Sure, I could convert it to an object. However a class would not be schemaless and I would have to maintain it each time the underlying data changed even though I don't care what it is on the server. I could use a dictionary, but Json.Net doesn't seem to deserialize nested dictionaries, I could use `JObject` but then web api chokes during content negotiation when xml is requested. Not being snarky, I just don't understand where people are confused - isn't this just basic content negotiation stuff? – George Mauer Aug 29 '14 at 20:45
  • @GeorgeMauer Why not deserialize to an anonymous type? Then serialize the anonymous type to JSON or XML. Or use Json.NET to convert it to XML, which is supported in their documentation. – mason Aug 29 '14 at 20:47
  • @mason I would love to, do you know how to do that? I've been playing aroudn with it and I can't seem to figure it out. As I mentioned in the question, `DeserializeAnonymousType` doesn't actually do what it sounds like it does - you still have to provide a template type for exactly how everything should be deserialzed. If I could convert `JObject` to an anonymous type that would work great. – George Mauer Aug 29 '14 at 20:49
  • You are asking how to do something using the API without having to code it yourself, that cannot be done in the API. When people tell you that the API does not support it you tell them that they are just restating the problem. I don't know what you are expecting to get here. If it's not supported by the API, then it's not supported by the API. Period. Write your own function. If you need help writing that function, I'm sure the people here will help you with that, but nobody can just "magick" new functionality into the API for you. – Nick Sep 02 '14 at 17:52
  • @Nick I've come to that conclusion as well. The thing is that nobody really said "this is not possible because X". Something about how I asked the question clearly caused a lot of confusion (though I'm really not sure what - this is a *very* straightforward thing that it's shocking is not supported). I actually sat down and did a longer investigation of what the issue is as well as an idiomatic way to recover from it. See my answer for what is actually going on under neath the hood. – George Mauer Sep 02 '14 at 19:11

3 Answers3

0

As their documentation describes, Json.NET can convert directly from JSON to XML. Here's the example from their documentation:

string json = @"{
  '?xml': {
    '@version': '1.0',
    '@standalone': 'no'
  },
  'root': {
    'person': [
      {
        '@id': '1',
        'name': 'Alan',
        'url': 'http://www.google.com'
      },
      {
        '@id': '2',
        'name': 'Louis',
        'url': 'http://www.yahoo.com'
      }
    ]
  }
}";

XmlDocument doc = (XmlDocument)JsonConvert.DeserializeXmlNode(json);
// <?xml version="1.0" standalone="no"?>
// <root>
//   <person id="1">
//   <name>Alan</name>
//   <url>http://www.google.com</url>
//   </person>
//   <person id="2">
//   <name>Louis</name>
//   <url>http://www.yahoo.com</url>
//   </person>
// </root>

So either pass the JSON along as a string (unmodified) or convert to XML based on the requested content type.

mason
  • 31,774
  • 10
  • 77
  • 121
  • Yup, but how would that fit in with web api content negotiation? – George Mauer Aug 29 '14 at 20:53
  • @GeorgeMauer Of course it can help. `content negotiation` is not a magic. You will read the `Accept` header from request, and then serialize the content accordingly(xml or json) (BTW: i have done it many times with WCF manually) – L.B Aug 29 '14 at 21:03
  • @L.B Right, I can do all sorts of things manually - if I wanted to I could even write an OWIN component that does things super-manually. But I'm trying to do it the way that's idomatic to WebApi and I don't see how this would fit into that scheme. – George Mauer Aug 29 '14 at 21:10
  • @GeorgeMauer You will have to write it :) – L.B Aug 29 '14 at 21:11
  • @L.B think through the process though - what you're returning doesn't differ in any fundamental way from any other object. At best you can return a `JObject`, then create a custom `IContentNegotiator` to detect that, then create a custom `MediaTypeFormatters` for each type you want that object to serialze to, which is a hack on the concept of `MediaTypeFormatters` to begin with but what else can you do? And all that for something that should be stupid simple. There's simply no way that this is the best approach. As mason himself said, `JObject` => anon type or dictionary would work but how? – George Mauer Aug 29 '14 at 21:19
  • @GeorgeMauer I have never used WebApi but,with WCF, you can return a *Message* where you set the HTTP content and headers manually. So it is all about writing a `ToSomething(object o,responseType)` method. – L.B Aug 29 '14 at 21:23
  • Right, you can do that, but that doesn't fit the WebApi idiom. Again, I can do this manually a dozen different ways, I can write a recursive funciton to rewrite a `JObject` to a dictionary, the issue is not that I don't know any way to do it is that I don't know a way of doing it that is idomatic and won't lead to a big ol' maintenance headache – George Mauer Aug 29 '14 at 21:30
  • 1
    @GeorgeMauer I see what you expect but don't think you will get it the way you expect. – L.B Aug 29 '14 at 21:32
0

So after a lengthy investigation I've managed to boil the problem down to this:

  • Web Api by default uses the (now basically abandoned) XmlSerializer when content type of xml is specified
  • XmlSerializer cannot serialize JObjects
  • There is no built-in way to convert JObjects of arbitrary nesting depth to dictionaries (although a custom recursive function to do this wouldn't be terribly hard).

So the way to attack the problem is to implement your own xml formatter that converts to dictionaries or uses something like XSerializer. This sounds simple, but in a related 30 minute-long spike I was unable to get my custom formatter to fire.

In the end I simply removed the xml formatter as it's not strictly necessary for my api and is more trouble than it's worth.

George Mauer
  • 117,483
  • 131
  • 382
  • 612
  • `JsonConvert.DeserializeObject(json)` can't handle arbitrary nesting depth dictionaries? I heard that it could, but I haven't tried it myself. I may have been misinformed. – Nick Sep 03 '14 at 14:09
  • @Nick `DeserializeObject` might be able to handle it but this is specifically about going the other direction - serializing. – George Mauer Sep 03 '14 at 15:50
  • You said "There is no built-in way to convert JObjects of arbitrary nesting depth to dictionaries (although a custom recursive function to do this wouldn't be terribly hard)." That is *deserialization*. – Nick Sep 04 '14 at 13:40
  • @Nick no, deserialization is going from a string (or some other flat format like bytes) to a structured object. Serialization is going from a structured object to a flat format. `JObject` to nested dictionary is neither, it's simply mapping. – George Mauer Sep 04 '14 at 17:39
  • You wonder why nobody can understand your question, you keep talking about serialization/deserialization yourself but now all of a sudden that's not what you want to do... Well, if `JsonConvert.DeserializeObject()` can handle nested dictionaries, maybe `JObject.ToObject()` can? – Nick Sep 05 '14 at 14:16
  • @Nick I suppose that might be it but no, I really *do* want serialization. Perhaps I wasn't very clear (I thought I was?), mapping `JObject` to dictionaries would be an intermediate step since dictionaries themselves can be serialized no problem. I hadn't tried `ToObject` before, but I just did. It simply returns another `JObject` just `as dynamic`. – George Mauer Sep 05 '14 at 14:47
-1

I achieved exactly this by casting my dynamic objects as List<Dictionary<string, object>>. Please clarify if this is what you need.

Okey, after you updated your question it is clear that you don't even need to serialize it to dictionary. Just use Json.NET (It's built in in WebApi and it's being used as default JSON serializer btw).

Also, please make sure in future that you pass valid json.

Working example (WebApi2)

JSON Payload

{
    "name": "Dan",
    "children": [
        {
            "name": "Fred"
        },
        {
            "name": "Fannie",
            "age": 30,
            "children": {
                "own": [
                    {
                        "name": "Barney"
                    },
                    {
                        "name": "Angela"
                    }
                ],
                "adopted": [
                    {
                        "name": "Sven"
                    }
                ]
            }
        }
    ]
}

Code

public class DefaultController : ApiController
{
    [Route, HttpGet]
    public IHttpActionResult Test()
    {
        var json = "{\"name\": \"Dan\",\"children\": [{\"name\": \"Fred\"},{\"name\": \"Fannie\",\"age\": 30,\"children\": {\"own\": [{\"name\": \"Barney\"},{\"name\": \"Angela\"}],\"adopted\": [{\"name\": \"Sven\"}]}}]}";

        var obj = JsonConvert.DeserializeObject(json);

        return Ok(obj);
    }
}

Response enter image description here

Stan
  • 25,744
  • 53
  • 164
  • 242
  • What happens if you send an `Accept` header containing `application/xml` to your Test method? – Brian Rogers Aug 30 '14 at 19:02
  • @BrianRogers By default WebApi cannot deserialize object that is not defiend to XML. Hoever you can remove default XML serializer and add your own that will do it. http://stackoverflow.com/a/19085236/440611 – Stan Aug 30 '14 at 19:14
  • This is just a restatement of the issue I mentioned where WebApi can serialize `JObject` to json but not to xml. The frustrating thing is that an anonymous type or dictionaries *can* be serialized. So the trick is to convert a `JObject` to one of these. – George Mauer Aug 31 '14 at 05:17