How to inline JSON structure with System.Text.Json

Viewed 100

I am looking for the equivalent of Golang's json: "inline" tag with C#'s System.Text.Json.

For example, I have the following class structure in C#:

class Outer{
    public string Hello;
    public Inner TheInner;
}

class Inner{
    public string Earth;
    public string Moon;
}

And I want the serialized and deserialized JSON text to be

{
   "Hello" : "example_value_01",
   "Earth" : "example_value_02",
   "Moon" : "example_value_03"
}

In Golang, I can achieve this with the following structure definition


type Outer struct{
    Hello string
    TheInner Inner `json: "inline"`
}

type Inner struct{
   Earth string
   Moon string
}

However, I cannot find a decent way to do this in C#'s System.Text.Json.

2 Answers

This solution uses reflexion, so is independant of number of string items in the class Inner and independant too of name of Inner Class, just you have to respect if its property or field:

public class Outer
{
    public string? Hello;
    public Inner? TheInner { get; set; }
}

public class Inner
{
    public string? World1;
    public string? World2;
    public string? World3;
}

the program using the nested class:

        var json= "{\"Hello\": \"hello\",\"World1\":\"world1\", \"World2\": \"world2\", \"World3\": \"world3\"}"; 
        var ou = deserialize(json);

        json = serialize(ou);

the methods serialize and deserialize:

        public Outer deserialize(string json)
        {
            var fieldhello = typeof(Outer).GetFields().First();
            var propinner = typeof(Outer).GetProperties().First();

            var subfieldsinner = typeof(Inner).GetFields().ToArray();
            var document = JsonDocument.Parse(json);
            JsonElement root = document.RootElement;

            var outer = new Outer();              
            var inner = new Inner();

            var innerstArr = subfieldsinner.Select(f => (field: f, value: root.TryGetProperty(f.Name, out var item) ? item.GetString() : null));

            foreach(var p in innerstArr)
                p.field.SetValue(inner, p.value);

            string? hellost = root.TryGetProperty(fieldhello.Name, out var item) ? item.GetString() : null;

            fieldhello.SetValue(outer, hellost);
            propinner.SetValue(outer, inner);

            return outer;
        }


        public string serialize(Outer outer)
        {
            var fieldhello = outer.GetType().GetFields().First();
            var propinner = outer.GetType().GetProperties().First();

            var inn = (Inner) propinner.GetValue(outer, null);

            var subfieldsinner = inn.GetType().GetFields().ToArray();            

            using var ms = new MemoryStream();
            using var writer = new Utf8JsonWriter(ms);

            writer.WriteStartObject();

            writer.WriteString(fieldhello.Name, (string?)fieldhello.GetValue(outer));
            foreach(var f in subfieldsinner)
                writer.WriteString(f.Name, (string?)f.GetValue(inn));

            writer.WriteEndObject();
            writer.Flush();

            string json = Encoding.UTF8.GetString(ms.ToArray());
            return json;
        }

To reach it in c# , you don't need any custom classes at all,you can use a dictionary

var dict = new Dictionary<string, string> {
                           {"Hello","example1"},
                           {"World","example2"}
                           };
                           
var json= System.Text.Json.JsonSerializer.Serialize(dict,new JsonSerializerOptions { WriteIndented = true});    

result

{
  "Hello": "example1",
  "World": "example2"
}

but if you want it a hard way it is much easier to make it using Newtonsoft.Json since Text.Json needs a custom serializer for almost everything

using Newtonsoft.Json;

    var json = SerializeOuterObj(obj, "TheInner");
    obj = DeserializeOuterObj(json);

public string SerializeOuterObj(object outerObj, string innerObjectPropertyName)
{
    var jsonParsed = JObject.FromObject(outerObj);
    var prop = jsonParsed.Properties().Where(i => i.Name == innerObjectPropertyName).First();
    prop.Remove();
    foreach (var p in ((JObject)prop.Value).Properties())
        jsonParsed.Add(p.Name, p.Value);
    return jsonParsed.ToString();
}
public Outer DeserializeOuterObj(string json)
{
    var jsonParsed = JObject.Parse(json);
    var outer = jsonParsed.ToObject<Outer>();
    outer.TheInner = jsonParsed.ToObject<Inner>();
    return outer;
}
Related