23

I'm trying to properly write code to build a data structure to serialize into json.

I'm using json.net.

I don't want to create a bunch of classes to hold this data, as I thought there should be some classes that will already do this in json.net

I've already got all the data I need in a series of nested loops, and now I just want to add them to an object hierarchy before I run JsonConvert.SerializeObject on it.

I've already tried code like this, but it doesn't seem to work

JArray container = new JArray();

        container.Add(new JObject(new JProperty("name", "Client1"), new JProperty("projects", new JArray())));

        container[0].AddAfterSelf(new JObject(new JProperty("projects", new JArray())));            
        container[1].AddAfterSelf(new JObject(new JProperty("projects", "Project2")));
        container[1].AddAfterSelf(new JObject(new JProperty("projects", "Project3")));
        container.Add(new JProperty("name", "Client2"));            

        var test = JsonConvert.SerializeObject(container);

The problem is that when I use [i]. or ElementAt(i) to access somewhere in the structure, either .Add() is missing or .ElementAt isn't there. How do I step through the data structure to make this nicely output the below, or do I have to create my own container class for all of this?

This is the data format I am trying to make.

[
    {
    "name": "student1",
    "projects": 
    [
        {
        "name": "Project1",
        "tasks": 
                [
                    {
                    "name": "task1",
                    "id": 2
                    }
                ],
        "id": 6
        }
    ]
},
    {
    "name": "Student2",
    "projects": [
                {
                "name": "Project1",
                "tasks": [
                         {
                         "name": "Task2",
                         "id": 1
                         },
                         {
                         "name": "Task3",
                         "id": 3
                         },
                         {
                         "name": "Task4",
                         "id": 4
                         }
                         ],
                "id": 2

etc...

Jackson Pope
  • 14,520
  • 6
  • 56
  • 80
Chris Barry
  • 4,564
  • 7
  • 54
  • 89

3 Answers3

34

I think what you're asking is how to serialize complex business objects in json, but only expose certain properties.

In other words you already have a list of students, but you only want to send very specific data via json. If I'm wrong this answer won't suit your needs.

So assuming you have a list of students, with a projects property that has an inner property of tasks, this is how I do it without having to create loads of new classes, I use anonymous objects.

Once I've created my list of anonymous objects, I simply turn them into a json string.

As pointed out in the comments you don't need to use json.net, this functionality is available in the framework, add a reference to System.Web.Extensions.dll then

using System.Web.Script.Serialization;

var jsonStudents = new List<object>();

foreach (var student in students)
{
    jsonStudents.Add(new
    {
        student.Id,         //anonymous properties automatically pick up the name of the property you pass them, this will be called Id
        FullName = student.FirstName + " " + student.LastName, //if you want to name a property yourself use this notation
        Projects = student.Projects.Select(p => new //this will be an enumerable of nested anonymous objects, we're partially selecting project properties
        {
            p.Id,
            p.Name,
            Tasks = p.Tasks.Select(t => new //nesting another level
            {
                t.Id,
                t.Name
            })
        })
    });
}

var serializer = new JavaScriptSerializer();

var jsonString = serializer.Serialize(jsonStudents);

If you really want to use loops you can do this to allow you to do more complicated things in the creating of the projects and tasks:

var jsonStudents = new List<object>();

foreach (var student in students)
{
    var tempStudent = new
    {
        student.Id,         //anonymous properties automatically pick up the name of the property you pass them, this will be called Id
        FullName = student.FirstName + " " + student.LastName, //if you want to name a property yourself use this notation
        Projects = new List<object>()
    };

    foreach (var project in student.Projects)
    {
        var tempProject = new {
            project.Id,
            project.Name,
            Tasks = new List<object>()
        };

        foreach (var task in project.Tasks)
        {
            tempProject.Tasks.Add(new {
                task.Id,
                task.Name
            });
        }

        tempStudent.Projects.Add(tempProject);
    }

    jsonStudents.Add(tempStudent);
}

var serializer = new JavaScriptSerializer();

var jsonString = serializer.Serialize(jsonStudents);
mattmanser
  • 5,719
  • 3
  • 38
  • 50
  • Hmm, this looks pretty clever and seems to depend on the linq2sql relationships. A neat solution. Do you know if this helps with performance as the compiler should know which properties it needs for the sub objects, and as such can be clever with the query (more of a linq2sql question) I was planning to optimize my data access once it was working. – Chris Barry May 17 '11 at 15:23
  • I've used this with POCOs, Linq2Sql and with LinqToEntities, you can do it with any .Net object. There's actually no Linq2Sql in my answer, I'm not entirely sure what you mean, though I think you're using Linq2Sql when you just mean Linq. Performance wise, the anonymous classes are actually declared in the IL afaik so it's really just saving you lines of code, all the rest of it is pretty much what you'd have to do if you were doing it using models as you'd described. – mattmanser May 17 '11 at 15:52
  • IMO this is the best answer... You dont even need json.net for this, just use the built in json serializer – Allen Rice May 20 '11 at 12:10
  • The reason I mentioned Linq2Sql was becuase of the student.Projects call, how does student know that it can refer to Projects? – Chris Barry May 20 '11 at 13:03
  • I'm guessing that is actually something anonymous. But does this answer help me with my series of nested loops? What if I want to grab some more data in the middle of the statement? – Chris Barry May 23 '11 at 09:19
  • Hey, please can you update your example to show how I could build this structure inside a set of loops, otherwise it does not answer my specific question. Or could you show how I could make data access calls during this statement, in a looping fashion. – Chris Barry May 23 '11 at 20:16
  • Added the looping example, I'm assuming you've already got a populated Student object here with all the projects and tasks loaded, but you could be using Linq2Sql to do this for you automatically. – mattmanser May 25 '11 at 12:51
11

this is the code which generates the exact output from your question (requires a using Newtonsoft.Json.Linq;):

var json = new JArray(
                new JObject(
                    new JProperty("name", "student1"),
                    new JProperty("projects", 
                        new JArray(
                            new JObject(
                                new JProperty("name", "Project1"),
                                new JProperty("tasks", 
                                    new JArray(
                                        new JObject(
                                            new JProperty("name", "task1"),
                                            new JProperty("id", 2)
                                            )
                                        )
                                    ),
                                new JProperty("id", 6)
                            )
                        )
                    )
                ),
                new JObject(
                    new JProperty("name", "student2"),
                    new JProperty("projects", 
                        new JArray(
                            new JObject(
                                new JProperty("name", "Project1"),
                                new JProperty("tasks", 
                                    new JArray(
                                        new JObject(
                                            new JProperty("name", "task2"),
                                            new JProperty("id", 1)
                                            ),
                                        new JObject(
                                            new JProperty("name", "task3"),
                                            new JProperty("id", 3)
                                            ),
                                        new JObject(
                                            new JProperty("name", "task4"),
                                            new JProperty("id", 4)
                                            )
                                        )
                                    ),
                                new JProperty("id", 2)
                            )
                        )
                    )
                )
            );
var jsonString = json.ToString();

I believe using the Json.Net Linq syntax has the great advantage that the resulting C# code can be formatted so that it almost has the same structure as the JSON you're trying to generate.

UPDATE

If you want to manipulate the Json object once it has been built, look at this example which builds the outer array with only one student, and then appends another one:

// create an isolated Student instance:
var student2 = new JObject(
                    new JProperty("name", "student2"),
                    new JProperty("projects", 
                        new JArray(
                            new JObject(
                                new JProperty("name", "Project1"),
                                new JProperty("tasks", 
                                    new JArray(
                                        new JObject(
                                            new JProperty("name", "task2"),
                                            new JProperty("id", 1)
                                            ),
                                        new JObject(
                                            new JProperty("name", "task3"),
                                            new JProperty("id", 3)
                                            ),
                                        new JObject(
                                            new JProperty("name", "task4"),
                                            new JProperty("id", 4)
                                            )
                                        )
                                    ),
                                new JProperty("id", 2)
                            )
                        )
                    )
                );

var json = new JArray(
                new JObject(
                    new JProperty("name", "student1"),
                    new JProperty("projects", 
                        new JArray(
                            new JObject(
                                new JProperty("name", "Project1"),
                                new JProperty("tasks", 
                                    new JArray(
                                        new JObject(
                                            new JProperty("name", "task1"),
                                            new JProperty("id", 2)
                                            )
                                        )
                                    ),
                                new JProperty("id", 6)
                            )
                        )
                    )
                )
            );

// now, add the student2 instance to the array:
json                             // which is an JArray
    .Last                        // gets the last Array item, i.e. "student1"
    .AddAfterSelf(student2);     // adds this which hence becomes the new last one

The idea is that you can apply the very same principle to any other portion of the structure in the same way.

HTH...

mthierba
  • 5,587
  • 1
  • 27
  • 29
  • Hi Thanks for the answer, but I want to build the json in a series of loops, ie not all at the same time? If I have to do it this way, I think I have to build it in one step, correct? Or can I modify the data structure after it has been built? – Chris Barry May 15 '11 at 21:58
  • No, you don't have to build it all in one step, you can also modify the structure later. See my updated answer above. – mthierba May 15 '11 at 22:26
  • OK, that's closer to what I need, but what if I want to add elements further down the tree? And what if I want to Add, rather than AddAfterSelf? I've got a series of nested loops running performing data access (I understand this could be bad for performance, but I'm not trying to solve that issue for now). I hope that makes sense. When I try to do container.Last.Last.AddAfterSelf(new JObject(new JProperty("test", "testItem"))); I get the error Can not add Newtonsoft.Json.Linq.JObject to Newtonsoft.Json.Linq.JObject. Do you see what I am trying to do? – Chris Barry May 15 '11 at 22:59
  • The error occurs because `container.Last.Last` returns the "projects" _property_, hence you can only add another `JProperty` after this, not a `JObject`. If you actually wanted to add a new `JObject` to the "projects" array, do this: `container.Last.SelectToken("projects").Last.AddAfterSelf(new JObject(...))`. This gets the "projects" property from the last item in _container_, and adds a new `JObject` to the "projects" array. So, with a combination of the `First` and `Last` properties, and the `SelectToken()` and `AddAfterSelf()` methods you should be able to achieve what you're after. – mthierba May 15 '11 at 23:28
  • So close it seems! Now the Array doesn't seem to like this, becuase it is empty, so the .Last can't select anything to add after? If you can tell me how to do this another way I can do that, thanks so much for all your help! – Chris Barry May 16 '11 at 00:02
  • Let's say the "projects" array was empty, and you wanted to add its first item to it, you'd actually have to create a new array like this: `.Last.SelectToken("projects").Replace(new JArray(new JObject(.....)));`. Once at least one item is in the array, the `First/Last` syntax works. – mthierba May 16 '11 at 05:56
  • BTW, even though this is all possible using the Json.Net API, it obviously gets quite messy at some point. For that scenario, I would most likely define a small domain model, containing `Student`, `Project`, `Task` (etc.) types, do all the dynamic composition using those classes (and let the compiler help me with type-checking), and use Json.Net only for a one-off serialization step, to send the results across the wire (or wherever). Produces much more readable code, and potentially gets you there much faster. Are there any reasons why you can't do it in that ("traditional") way? – mthierba May 16 '11 at 06:07
  • No reason at all, I really just wanted to see which was the best way to do it. Within each of the Student Project Task types would i use JObjects, JArrays etc.. or Lists & Dictionaries? – Chris Barry May 16 '11 at 08:58
  • I've just added what I did as a separate answer below, but you will get the bounty unless there is a better way to do what I have done below. Thank you so much for your help. – Chris Barry May 16 '11 at 14:07
  • Hey, everyone else has actually upvoted the other question, but I got to the solution I wanted through your help. I'll ask them to answer my question, otherwise give the bounty to you. – Chris Barry May 23 '11 at 20:15
5

In the end I have used these Models.

public class JStudent
{
    public List<JProject> projects = new List<JProject>();
    public string name;
    public string id;
}
public class JProject
{
    public List<JTask> tasks = new List<JTask>();
    public string name;
    public string id;
}
public class JTask
{
    public string name;
    public string id;

}

It is now working perfectly. Is there any better way to do this?

Chris Barry
  • 4,564
  • 7
  • 54
  • 89
  • 4
    If I were you, I would simply build your object hierarchy like you are doing here and leave all the serialization to json.net. If you have specific needs in the serialization, you can control that with attributes: http://james.newtonking.com/projects/json/help/ – Bashwork May 19 '11 at 13:57
  • Why is this answer not voted higher? This is absolutely awsome. Thank you! – Thomas May 04 '16 at 15:11