1

I have a list of key-value pair of json property path and its value,

Key: $.orderNumber                                      Value: "100001"
Key: $.orderedOn                                        Value: "01-01-2021"
Key: $.product.name                                     Value: "hard"
Key: $.product.type                                     Value: "plastic" 
Key: $.contacts[?(@.contactType == 'primary')].phone    Value: "101010101010" 
Key: $.contacts[?(@.contactType == 'secondary')].phone  Value: "2020202020"

and wanted to create a JSON object or JSON string like below

{
  "orderNumber": "100001",
  "orderedOn": "01-01-2021",
  "product": {
    "type": "hard",
    "name": "plastic"
  },
  "contacts": [
    {
      "contactType": "primary",
      "phone": "101010101010"
    },
    {
      "contactType": "secondary",
      "phone": "202020202020"
    }
  ]
}

is it possible to create a JSON object using JSON PATH or I need to check the write all properties manually?

I wanted to create JSON objects dynamically.

I have tried the Json.net Unflatten but it is not covering the conditional array key.

11
  • Are you using .Net or .Net-Core? Commented Mar 9, 2021 at 17:43
  • 1
    Been asked a few times but it's not implemented out of the box in Json.NET. Along with the question linked by gunr2171, see how to set the value of a json path using json.net, Json.Net, how to create json dynamically by path? and a few others if I recall correctly. And actually converting [?(@.contactType == 'primary')] to an object with a "contactType": "primary" property looks nontrivial. Commented Mar 9, 2021 at 18:34
  • @Amy yes, I am using .Net 5 Commented Mar 10, 2021 at 6:53
  • 1
    I know. As I said, it's unfortunately not implemented out of the box, and nobody ever posted a custom implementation here. Looks like somebody closed your question as a duplicate of Build JObject from JSONPath, to which the accepted answer is No.. So on the one hand your question is a duplicate but on the other hand the answer given is kind of useless. Do you need this reopened in case somebody wants to write all the code required to parse a JSONPath and populate a JObject from it? Commented Mar 10, 2021 at 7:04
  • 1
    @UmairAnwaar could you please share your custom solution? I am facing the same problem. Commented Apr 14, 2024 at 14:12

1 Answer 1

1

Static Solution:

No dynamic solution was found, so I created a static solution:

Models: Defines the C# classes representing the structure of the JSON object.

class Order
{
    public string OrderNumber { get; set; }
    public string OrderedOn { get; set; }
    public Product Product { get; set; }
    public List<Contact> Contacts { get; set; }
}

class Product
{
    public string Type { get; set; }
    public string Name { get; set; }
}

class Contact
{
    public string ContactType { get; set; }
    public string Phone { get; set; }
    public string Address { get; set; }
}

Mapping Function: Maps JSON paths and values to corresponding properties in the Order object.

Order MapOrderObjectWithJsonPath(Order order, string jsonPath, string value)
{
    // First initialize the order object if necessary for nested objects.
    InitializeObjects(order, jsonPath);

    // Use switch-case for better performance.
    switch (jsonPath)
    {
        case "$.orderNumber":
            order.OrderNumber = value;
            break;
        case "$.orderedOn":
            order.OrderedOn = value;
            break;
        case "$.product.type":
            order.Product.Type = value;
            break;
        case "$.product.name":
            order.Product.Name = value;
            break;

        // In my case, contact type is an identifier.
        case "$.contacts[?(@.contactType == 'primary')].phone":
            order.Contacts.FirstOrDefault(x => x.ContactType == "primary").Phone = value;
            break;
        case "$.contacts[?(@.contactType == 'primary')].address":
            order.Contacts.FirstOrDefault(x => x.ContactType == "primary").Address = value;
            break;
        case "$.contacts[?(@.contactType == 'secondary')].phone":
            order.Contacts.FirstOrDefault(x => x.ContactType == "secondary").Phone = value;
            break;
        case "$.contacts[?(@.contactType == 'secondary')].address":
            order.Contacts.FirstOrDefault(x => x.ContactType == "secondary").Address = value;
            break;
    }
}

Object Initialization: Initializes nested objects (Product, Contacts) based on JSON paths.

public void InitializeObjects(Order order, string jsonPath)
{
    // Initialize the product object if it is not initialized before.
    if (jsonPath.Contains(".product.") && order.Product == null)
        order.Product = new Product();

    // For the contacts array, initialize and check the respective contact element.
    // If it is not in the list mentioned in the JSON path, create that element and add it to the contact list.
    else if (jsonPath.Contains("$.contacts[?(@.contactType == 'primary')]"))
    {
        if (order.Contacts == null) order.Contacts = new List<Contact>();

        if (!order.Contacts.Any(x => x.ContactType.Equals("primary")))
        {
            Contact primaryContact = new Contact();
            primaryContact.ContactType = "primary";
            order.Contacts.Add(primaryContact);
        }
    }
    else if (jsonPath.Contains("$.contacts[?(@.contactType == 'secondary')]"))
    {
        if (order.Contacts == null) order.Contacts = new List<Contact>();

        if (!order.Contacts.Any(x => x.ContactType.Equals("secondary")))
        {
            Contact secondaryContact = new Contact();
            secondaryContact.ContactType = "secondary";
            order.Contacts.Add(secondaryContact);
        }
    }
}

Usage:

using Newtonsoft.Json;

// Create an empty Order object 
Order order = new Order();

// List of JSON paths and corresponding values, considering that you have that in a list. 
List<(string jsonPath, string value)> jsonPathValues = new List<(string, string)>
{
    // Add your JSON paths and values here
    // Example: ("$.orderNumber", "100001"),
};

// Loop through each JSON path and value, mapping them to the order object.
foreach (var (jsonPath, value) in jsonPathValues)
{
    // Map the JSON path and value to the existing Order object
    MapOrderObjectWithJsonPath(order, jsonPath, value);
}

// Serialize the 'order' object into JSON with settings to handle null values for empty objects
string json = JsonConvert.SerializeObject(order, Formatting.None, new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore
});

// Now 'json' has the result
Sign up to request clarification or add additional context in comments.

4 Comments

This is a surprising outcome, I thought STJ.JsonDocument would support such a usecase like yours. Newtonsoft should have something similar, at least that was my expectation. Bookmarking this answer to see if I can find anything in STJ for this.
My expectation was the same for Newtonsoft JSON, but there is a complication when handling arrays. While it's easy to use a dictionary and reflection to create a JSON object for objects, or even without creating a C# object using JObject to create a JSONObject using JSONPath, arrays introduce complications, especially when JSONPath involves conditional statements
Were you able to twist JsonPatchDocument<T> to your needs? Just spit balling now that you have already achieved it. I guess I am still unable to believe this happened.
No, I didn't modify the JsonPatchDocument<T>. Instead, I created a custom solution that works as I described. However, it's not dynamic; I need to handle new fields separately

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.