3

We have a project which using System.Text.Json in .NET 5 instead of Newtonsoft JObject. Using Newtonsoft, it is pretty easy to replace dynamic JSON data e.g. as shown below:

siteDataObject["student"] = JArray.FromObject(studentservice.GetStudents());

When studentservice.GetStudents() is return List as below structure

internal class Student {
    public int Id { get; set; }
    public string Name { get; set; }
    public string ContactPhone { get; set; }

    public IEnumerable<MedicalRecord> MedicalRecords { get; set; }
}

internal class MedicalRecord {
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime RecordDate { get; set; }
    public IEnumerable<DiseaseLog> DiseaseLogs{ get; set; }
}

internal class DiseaseLog {
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime LogDate { get; set; }
}

but in System.Text.Json

foreach (var element in doc.RootElement.EnumerateObject()) {
    if (element.Name == "student") {
        writer.WritePropertyName(element.Name);
    
    }
    else {
        element.WriteTo(writer);
    }
}

I don't know how to convert List<student> into JSON array data, when student class have many properties with multi collection inside.

Can anyone advise how to convert it ?

To clarify, I need to propose the full code for this, I have a dynamic json string and want to replace element : students into new record, the code will be

var dynamicJson = @"{'roomid':1,'roomcode':'Code001','students':[1],'contentdata':'say hello','footerdata':'cookie policy'}";
using MemoryStream stream = new MemoryStream();
using Utf8JsonWriter writer = new Utf8JsonWriter(stream);
using var dynamicDocument = JsonDocument.Parse(dynamicJson);
writer.WriteStartObject();
foreach (var element in dynamicDocument.RootElement.EnumerateObject())
{
    if (element.Name == "students")
    {
        // unknown how to modify the student record into array
    }
    else
        element.WriteTo(writer);
}
writer.WriteEndObject();
stream.Flush();
var modifyJson = Encoding.UTF8.GetString(stream.ToArray());

I know how to modify student value , if student element is string, but I don't know how to modify it into array, by using simple code. As student have multi class inside.

My expected result should be

{
    "roomid": 1,
    "roomcode": "Code001",
    "students": [
        {
            "id": 1,
            "Name": "Wilson",
            "ContactPhone": "123-122-3311",
            "MedicalRecords": [
                {
                    "id": 101,
                    "Name ": "Medial record 101011",
                    "RecordDate": "2021-12-31",
                    "DiseaseLogs": [
                        {
                            "id": 18211,
                            "Name ": "Patient Log 19292",
                            "LogDate": "2020-1-31"
                        },
                        {
                            "id": 18212,
                            "Name ": "Patient Log 2911w",
                            "LogDate": "2020-3-31"
                        }
                    ]
                }
            ]
        }
    ],
    "contentdata": "say hello",
    "footerdata": "cookie policy"
}
dbc
  • 104,963
  • 20
  • 228
  • 340
Carrie Kaski
  • 133
  • 7
  • 2
    `JsonElement` and `JsonDocument` are read-only, they can't be used to dynamically create a JSON document. In .NET 6 `System,Text.Json.Nodes` was added which allows for creation and modification of a JSON document object model. But maybe you could explain more about what you are doing? As it is it's not entirely clear what the fragment of code from System.Text.Json is intended to accomplish. If you could explain exactly what you need to accomplish and where you are stuck (i.e. a [mcve]) maybe we could suggest how to accomplish it. – dbc Apr 23 '22 at 15:23
  • Possibly [Modifying a JSON file using System.Text.Json](https://stackoverflow.com/q/58997718/3744182), [System.Text.Json.JsonElement ToObject workaround](https://stackoverflow.com/q/58138793/3744182) or [Equivalent of JObject in System.Text.Json](https://stackoverflow.com/a/65634585/3744182) will answer your question. – dbc Apr 23 '22 at 15:25
  • I know .Net6 or JOject is easy to do, but my project is .Net5 which cannot do this. I try to use clone, but the list is not allow for clone – Carrie Kaski Apr 23 '22 at 18:28
  • Do you care about preserving the order of properties in the root object? According to the [JSON RFC](https://www.rfc-editor.org/rfc/rfc8259#section-1) an object is an *unordered collection of zero or more name/value pairs* so you probably don't, but it can't hurt to check. – dbc Apr 23 '22 at 18:48

2 Answers2

1

I think this is what you want (array or single nested object):

var student = new Student()
{
    Name = "Student",
    ContactPhone = "contact",
    Id = 1,
    MedicalRecords = new List<MedicalRecord>()
    {
        new MedicalRecord()
        {
            Name = "Medical Record 1",
            RecordDate= DateTime.Now ,
            Id = 1 ,
            MedicalRecords = new List<DiseaseLog>()
            {
                new DiseaseLog(){ Name = "some disease" ,
                    LogDate = DateTime.Now, Id =1  }
            }
        }
    }
};

var data = System.Text.Json.JsonSerializer.Serialize(student);
Console.WriteLine(data);


var list = new List<Student>();
list.Add(student);

var arrayData = System.Text.Json.JsonSerializer.Serialize(list);

Console.WriteLine(arrayData);
Reza Heidari
  • 1,192
  • 2
  • 18
  • 23
1

In .NET 5 there is no modifiable JSON Document Object Model built into to System.Text.Json. JsonDocument is read-only, and System.Text.Json.Nodes was only introduced in .NET 6. Thus, the easiest way to deserialize, modify and re-serialize free-form JSON in .NET 5 is to deserialize to some partial data model, with unknown values bound into a dictionary.

If you do not care about the order of properties at the root level, you could deserialize to a model with a public object students { get; set; } property, and bind the remaining elements to a JsonExtensionData overflow dictionary:

public class RootObject
{
    public object students { get; set; }

    [System.Text.Json.Serialization.JsonExtensionDataAttribute]
    public IDictionary<string, object> ExtensionData { get; set; }
}

Then deserialize, modify and re-serialize as follows:

var students = new List<Student> { /* Initialize these as required... */ };

var dynamicJson = @"{""roomid"":1,""roomcode"":""Code001"",""students"":[1],""contentdata"":""say hello"",""footerdata"":""cookie policy""}";

var root = JsonSerializer.Deserialize<RootObject>(dynamicJson);

root.students = students;

var modifyJson = JsonSerializer.Serialize(root, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true });

Which results in

{
  "students": [
    {
      "id": 1,
      "name": "Wilson",
      "contactPhone": "123-122-3311",
      "medicalRecords": [
        {
          "id": 101,
          "name": "Medial record 101011",
          "recordDate": "2021-12-31T00:00:00",
          "diseaseLogs": [
            {
              "id": 18211,
              "name": "Patient Log 19292",
              "logDate": "2020-01-31T00:00:00"
            },
            {
              "id": 18212,
              "name": "Patient Log 2911w",
              "logDate": "2020-03-31T00:00:00"
            }
          ]
        }
      ]
    }
  ],
  "roomid": 1,
  "roomcode": "Code001",
  "contentdata": "say hello",
  "footerdata": "cookie policy"
}

the students property must be declared as object because the input JSON already has an array containing a single integer value; declaring it as public List<Student> students { get; set; } would result in a deserialization when initially loading the JSON.

Demo fiddle #1 here.

If you do care about the order of properties at the root level, you could deserialize to an OrderedDictionary (an old order-preserving non-generic dictionary dating from .NET Framework 2.0 which is still around and supported), overwrite the "students" value, and re-serialize:

var students = new List<Student> { /* Initialize these as required... */ };

var dynamicJson = @"{""roomid"":1,""roomcode"":""Code001"",""students"":[1],""contentdata"":""say hello"",""footerdata"":""cookie policy""}";

var root = JsonSerializer.Deserialize<OrderedDictionary>(dynamicJson);

root["students"] = students;

var modifyJson = JsonSerializer.Serialize(root, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true });

Which results in

{
  "roomid": 1,
  "roomcode": "Code001",
  "students": [
    {
      "id": 1,
      "name": "Wilson",
      "contactPhone": "123-122-3311",
      "medicalRecords": [
        {
          "id": 101,
          "name": "Medial record 101011",
          "recordDate": "2021-12-31T00:00:00",
          "diseaseLogs": [
            {
              "id": 18211,
              "name": "Patient Log 19292",
              "logDate": "2020-01-31T00:00:00"
            },
            {
              "id": 18212,
              "name": "Patient Log 2911w",
              "logDate": "2020-03-31T00:00:00"
            }
          ]
        }
      ]
    }
  ],
  "contentdata": "say hello",
  "footerdata": "cookie policy"
}

Demo fiddle #2 here.


In .NET 6 this all becomes easier through use of the System.Text.Json.Nodes editable JSON Document Object Model:

var dynamicJson = @"{""roomid"":1,""roomcode"":""Code001"",""students"":[1],""contentdata"":""say hello"",""footerdata"":""cookie policy""}";

var nodes = JsonSerializer.Deserialize<JsonObject>(dynamicJson);

nodes["students"] = JsonSerializer.SerializeToNode(students, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });

var modifyJson = nodes.ToString();

But in .NET 5 this is not possible. Demo fiddle #3 here.

dbc
  • 104,963
  • 20
  • 228
  • 340