2

I want that every JSON property name represents the path to it.

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string StreetNumber { get; set; }
}

Using Newtonsoft.Json I currently get this JSON:

{
   "firstName":"Homer",
   "lastName":"Simpson",
   "address":{
      "street":"Evergreen Terrace",
      "streetNumber":"742"
   }
}

Is it possible to get this JSON instead:

{
   "person.firstName":"Homer",
   "person.lastName":"Simpson",
   "person.address":{
      "person.address.street":"Evergreen Terrace",
      "person.address.streetNumber":"742"
   }
}
TheWho
  • 485
  • 3
  • 17
  • You would need to postprocess your JSON afterwards. Json.NET is a contract-based serializer, so if multiple `Address` objects appear in the graph, it's going to serialize them the same way using the same contract. You want a different contract (because different property names) for each occurrence of `Address` in the graph. Serializing to `JObject` and fixing the property names afterwards would seem to be the way to go. Does that answer your question? – dbc Mar 11 '21 at 15:10

3 Answers3

0

You can't do that, 'cause it's ambiguous. How would this file be deserialized?

In C#, it could be interpreted as...

public class Person
{
    public string Person.firstName { get; set; }
    public string Person.LastName { get; set; }
    public Address Person.Address { get; set; }
}

It's a invalid format, so your file must cause a Exception or a compilation error.

Try this on browser, using JSON.parse() method and you'll get a object with this structure (the property names with a dot).

0

this format is invalid . but : sample

        private static string _getCustomJsonFormat(object o, string _parentPath = "")
    {
        string str = "{" ;
        {

            var t = o.GetType();
            var props = t.GetProperties();
            if (props != null)
            {
                List<string> _kv = new List<string>();
                foreach (var i in props)
                {
                    List<string> _path = new List<string>() { };
                    if (_parentPath.Length == 0) _path.Add(t.Name);
                    _path.AddRange(_parentPath.Split('.').Select(m => m.Trim()).Where(m => m.Length > 0));
                    string _p = i.Name; 
                    object _v = i.GetValue(o);
                    Type _vt = _v.GetType();
                    _path.Add(_p);
                    if (_vt.IsNested)
                    {
                        // Get Object
                        _kv.Add("\"" + string.Join(".",_path) + "\":" + _getCustomJsonFormat(_v, string.Join(".", _path)));
                    }
                    else {
                        // Get Property
                        _kv.Add("\"" + string.Join(".", _path)+ "\":\"" + _v.ToString() + "\"");
                    }
                }
                str += string.Join(",", _kv);

            }
        }
        return str+" }";
    }

use :

            var p = new Person() { Address = new Address() { Street = "1", StreetNumber = "2" }, FirstName = "a", LastName = "b" };
        var x = _getCustomJsonFormat(p);
0

Json.NET will not serialize an object using JSONPaths as property names out of the box because it is a contract-based serializer. As such, it will serialize all instances of some type T in the same way wherever they are encountered in the serialization graph. You, conversely, want a slightly different contract for each instance encountered that reflects the path to the instance in the graph.

So, what are your options to get the JSON you require?

Firstly, you could serialize to JObject then postprocess it using the public static void Rename(this JToken token, string newName) extension method from this answer by Brian Rogers to Rename JProperty in json.net:

var person = new Person
{
    // Initialize your Person as required
};

var settings = new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
};
var jObject = JObject.FromObject(person, JsonSerializer.CreateDefault(settings));
foreach (var item in jObject.Descendants().OfType<JProperty>().Select(p => (Property: p, p.Path)).ToList())
{
    // The Rename extension method from https://stackoverflow.com/a/47269811/3744182
    item.Property.Rename("person." + item.Path);
}

var json = jObject.ToString();

Demo fiddle #1 here.

Secondly, you could rename the properties on the fly as you serialize by subclassing JsonTextWriter and overriding the WritePropertyName() methods:

public class NameRemappingJsonWriter : JsonTextWriter
{
    readonly Action<string> jsonWriterWritePropertyName;
    readonly Func<string, int, string, string> map;

    public NameRemappingJsonWriter(TextWriter textWriter, Func<string, int, string, string> map) : base(textWriter)
    {
        this.map = map ?? throw new ArgumentNullException(nameof(map));
        //Method to call a base-of-base-class method taken from this answer https://stackoverflow.com/a/32562464
        //By https://stackoverflow.com/users/5311735/evk
        //To https://stackoverflow.com/questions/2323401/how-to-call-base-base-method
        var ptr = typeof(JsonWriter).GetMethod("WritePropertyName", new[] { typeof(string) }).MethodHandle.GetFunctionPointer();
        jsonWriterWritePropertyName = (Action<string>)Activator.CreateInstance(typeof(Action<string>), this, ptr);
    }

    public override void WritePropertyName(string name) => WritePropertyName(name, true);

    public override void WritePropertyName(string name, bool escape)
    {
        jsonWriterWritePropertyName(name);
        WriteRaw(JsonConvert.ToString(map(name, Top, Path)));
        WriteRaw(":");
    }       
}

And then later:

var settings = new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
};
Func<string, int, string, string> map = (name, depth, parentPath) => "person." + parentPath;
using var textWriter = new StringWriter();
using (var jsonWriter = new NameRemappingJsonWriter(textWriter, map) { Formatting = Formatting.Indented })
{
    JsonSerializer.CreateDefault(settings).Serialize(jsonWriter, person);
}
var json = textWriter.ToString();

Note that, to get this to work, I had to call the base-of-base-class JsonWriter.WritePropertyName() methods, which is slightly sketchy and not recommended or supported natively in c#.

Demo fiddle #2 here.

dbc
  • 104,963
  • 20
  • 228
  • 340