I have some JSON Data which looks like this:
{
"response":{
"_token":"StringValue",
"code":"OK",
"user":{
"userid":"2630944",
"firstname":"John",
"lastname":"Doe",
"reference":"999999999",
"guid":"StringValue",
"domainid":"99999",
"username":"jdoe",
"email":"jdoe#jdoe.edu",
"passwordquestion":"",
"flags":"0",
"lastlogindate":"2013-02-05T17:54:06.31Z",
"creationdate":"2011-04-15T14:40:07.22Z",
"version":"3753",
"data":{
"aliasname":{
"$value":"John Doe"
},
"smsaddress":{
"$value":"5555555555#messaging.sprintpcs.com"
},
"blti":{
"hideemail":"false",
"hidefullname":"false"
},
"notify":{
"grades":{
"$value":"0"
},
"messages":{
"$value":"1"
}
},
"beta_component_courseplanexpress_1":{
"$value":"true"
}
}
}
}
I am using C# with JSON.NET to parse through the data. I've been able to sucessfully get data using this algorithm:
User MyUser = new User();
JToken data = JObject.Parse(json);
MyUser.FirstName = (string) data.SelectToken("response.user.firstname");
//The same for all the other properties.
The problem is with the data field. This field is based on user preferences mostly and data is only inserted as it is used. The fields are all custom and developers can put in as many as they want without restrictions. Essentially, it's all free form data. Also as you notice they can be nested really far with data.
I've tried to run:
MyUser.Data = JsonConvert.DeserializeObject<List<JToken>>((string) data.SelectToken("response.user.data");
which doesn't work.
How would you go about converting it to be used in a C# object?
You can access it via the JToken / JArray / JObject methods. For example, this will list all of the keys under the data:
public class StackOverflow_14714085
{
const string JSON = #"{
""response"": {
""_token"": ""StringValue"",
""code"": ""OK"",
""user"": {
""userid"": ""2630944"",
""firstname"": ""John"",
""lastname"": ""Doe"",
""reference"": ""999999999"",
""guid"": ""StringValue"",
""domainid"": ""99999"",
""username"": ""jdoe"",
""email"": ""jdoe#jdoe.edu"",
""passwordquestion"": """",
""flags"": ""0"",
""lastlogindate"": ""2013-02-05T17:54:06.31Z"",
""creationdate"": ""2011-04-15T14:40:07.22Z"",
""version"": ""3753"",
""data"": {
""aliasname"": {
""$value"": ""John Doe""
},
""smsaddress"": {
""$value"": ""5555555555#messaging.sprintpcs.com""
},
""blti"": {
""hideemail"": ""false"",
""hidefullname"": ""false""
},
""notify"": {
""grades"": {
""$value"": ""0""
},
""messages"": {
""$value"": ""1""
}
},
""beta_component_courseplanexpress_1"": {
""$value"": ""true""
}
}
}
}
}";
public static void Test()
{
var jo = JObject.Parse(JSON);
var data = (JObject)jo["response"]["user"]["data"];
foreach (var item in data)
{
Console.WriteLine("{0}: {1}", item.Key, item.Value);
}
}
}
Json.NET can actually parse to a dynamic if that is useful to you.
Which means you can do something like.
dynamic parsedObject = JsonConvert.DeserializeObject("{ test: \"text-value\" }");
parsedObject["test"]; // "text-value"
parsedObject.test; // "text-value"
parsedObject.notHere; // null
Edit: might be more suitable for you to iterate the values if you don't know what you are looking for though.
dynamic parsedObject = JsonConvert.DeserializeObject("{ test: { inner: \"text-value\" } }");
foreach (dynamic entry in parsedObject)
{
string name = entry.Name; // "test"
dynamic value = entry.Value; // { inner: "text-value" }
}
Related
I am trying to add the elements of a JArray to a JObject in C#. I have the solution in Java, but cannot figure out how to do the same in C#. Here is my Java code:
public static JSONObject[] fetchData(String dataFile, String arrayName) {
JSONArray jsonArray;
try {
jsonArray = extractObject_JSON(dataFile).getJSONArray(arrayName);
} catch (Exception e) {
// If Method Name is not matching with arrayName, then default will be taken
jsonArray = extractObject_JSON(dataFile).getJSONArray("default");
}
JSONObject[] jsonObject = new JSONObject[jsonArray.length()];
for (int i = 0; i < jsonArray.length(); i++) {
jsonObject[i] = jsonArray.getJSONObject(i);
}
return jsonObject;
}
and here is my C# code:
public static JObject FetchData(string testMethodName)
{
using (StreamReader r = new StreamReader("PathToFile"))
{
string jsonstring = r.ReadToEnd();
JObject obj = JObject.Parse(jsonstring);
JArray jsonArray = JArray.Parse(obj[testMethodName].ToString());
JObject jObject = new JObject();
for (int i = 0; i < jsonArray.Count; i++)
{
jObject[i] = jsonArray[i];
}
return jObject;
}
}
jsonArray in this code example returns:
{[
{
"loginId": "testuser1",
"userCase": "verify for user"
},
{
"loginId": "testuser2",
"userCase": "verify for user"
}
]}
The testMethodName would be LoginTest_E2E (see .json input file below)
{
"LoginTest_E2E": [
{
"loginId": "testuser1",
"userCase": "verify for user"
},
{
"loginId": "testuser2",
"userCase": "verify for user"
}
]
}
When I run my C# code I get the following error:
System.ArgumentException: 'Set JObject values with invalid key value: 0. Object property name expected.'
I would like the fetchData method to return a JObject of:
{
"loginId": "testuser1",
"userCase": "verify for user"
},
{
"loginId": "testuser2",
"userCase": "verify for user"
}
Does anyone know how to solve this in C#?
As you have it written, jObject is expecting a string value for the key of the property you are adding to it. At least that's my understanding judging from the fact that JObject extends IDictionary<string, JToken>: https://www.newtonsoft.com/json/help/html/t_newtonsoft_json_linq_jobject.htm
You're trying to give it an integer value as a key. Judging from your Java code, it looks like you meant to declare an array of JObjects, but you just declared one here:
JObject jObject = new JObject();
If this is the case, change it to JObject[] as #Selman said.
I'm trying to add a JSON string to an existing one but still haven't been able to do it successfully.
This is the original string: originalJSONString
{
"properties": [
"property1",
"property2"
]
}
And I want to add this to the original string: JSONStringIwantToAdd
{
"filter": {
"Name": "some filter name",
"Parameters": {
"LookupKey": "somekey",
"LookupValue": "somevalue"
}
}
}
To make a resulting string like this: finalJSONString
{
"properties": [
"property1",
"property2"
],
"filter": {
"Name": "some filter name",
"Parameters": {
"LookupKey": "somekey",
"LookupValue": "somevalue"
}
}
}
This is my direction so far but I'm getting null in propertiesJObject and can't figure out afterwards.
Is this even the right direction I'm going?
var originalJObj = JObject.Parse(originalJSONString);
var tobeaddedJObj = JObject.Parse(JSONStringIwantToAdd);
var propertiesJObject = originalJObj["properties"] as JObject;
propertiesJObject.Add(tobeaddedJObj);
var serializer = new JsonSerializer { ContractResolver = new CamelCasePropertyNamesContractResolver() };
var finalJSONString = JObject.FromObject(originalJObj, serializer).ToString();
Can someone please help me with this?
Thank You for your time!
JSON.NET includes functionality to do exactly what you need: JContainer.Merge
Note that Merge modifies the original object, rather than returning a new one:
var original = JObject.Parse(#"{
""properties"": [
""property1"",
""property2""
]
}");
var toAdd = JObject.Parse(#"{
""filter"": {
""Name"": ""some filter name"",
""Parameters"": {
""LookupKey"": ""somekey"",
""LookupValue"": ""somevalue""
}
}
}");
original.Merge(toAdd, new JsonMergeSettings
{
// union array values together to avoid duplicates
MergeArrayHandling = MergeArrayHandling.Union
});
Fiddle link: https://dotnetfiddle.net/o51GuA
You can do this without explicitly using a serializer.
This code adds the first property from JSONStringIwantToAdd to originalJSONString:
var originalJson = "{\r\n \"properties\": [\r\n \"property1\",\r\n \"property2\"\r\n ]\r\n}";
var extraJson = "{\r\n \"filter\": {\r\n \"Name\": \"some filter name\",\r\n \"Parameters\": {\r\n \"LookupKey\": \"somekey\",\r\n \"LookupValue\": \"somevalue\"\r\n }\r\n }\r\n}";
var original = JObject.Parse(originalJson);
var extra = JObject.Parse(extraJson);
var newProperty = extra.Children().First() as JProperty;
original.Add(newProperty.Name, newProperty.Value);
var newJson = original.ToString();
Output:
{
"properties": [
"property1",
"property2"
],
"filter": {
"Name": "some filter name",
"Parameters": {
"LookupKey": "somekey",
"LookupValue": "somevalue"
}
}
}
This should work, the main issue was not accessing the filter property when adding it to the jobject. I would recommend adding some safeguards like checking for existing properties, this may not be as performant as possible. If speed is important you may have to write your own serializer/deserializer for json.net.
[Test]
public void AugmentJsonObjectTest()
{
// Given
var originalString = "{ \"properties\": [ \"property1\", \"property2\" ]}";
var stringToBeAdded = "{ \"filter\": { \"Name\": \"some filter name\", \"Parameters\": { \"LookupKey\": \"somekey\", \"LookupValue\": \"somevalue\" } }}";
// When
var originalObject = JObject.Parse(originalString);
var objectToBeAdded = JObject.Parse(stringToBeAdded);
originalObject.Add("filter", objectToBeAdded["filter"]);
var mergedObjectAsString = originalObject.ToString();
// Then
var expectedResult =
"{ \"properties\": [ \"property1\", \"property2\" ], \"filter\": { \"Name\": \"some filter name\", \"Parameters\": { \"LookupKey\": \"somekey\", \"LookupValue\": \"somevalue\" } }}";
// Parsing and toString to avoid any formatting issues
Assert.AreEqual(JObject.Parse(expectedResult).ToString(), mergedObjectAsString);
}
using Expando
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using Newtonsoft.Json;
namespace jsonConcat {
class Program {
static void Main (string[] args) {
ExpandoObject inputData;
ExpandoObject appendData;
using (TextReader inputReader = new StreamReader ("originalData.json"))
using (TextReader appendReader = new StreamReader ("appendData.json")) {
inputData = JsonConvert.DeserializeObject<ExpandoObject> (inputReader.ReadToEnd ());
appendData = JsonConvert.DeserializeObject<ExpandoObject> (appendReader.ReadToEnd ());
}
foreach (var item in appendData) {
inputData.TryAdd (item.Key.ToString (), item.Value);
}
using (TextWriter outputWriter = new StreamWriter ("outputData.json")) {
outputWriter.Write (JsonConvert.SerializeObject (inputData));
}
}
}
}
Problem description
I need to receive JSON array of objects (with almost any shape) and store them to ES database using function IndexMany (or some similar bulk indexing function). I found some clumsy solution with one drawback - it doesn't set _id property in ES correctly (according to the id property in JSON object).
And also I would like to know if there is any more elegant way how to achieve my goal without casting every JArray item to string and back to ExpandoObject.
Additional info
Elasticsearch DB 7.5.1
NEST (7.6.1)
Newtonsoft.Json (12.0.3)
TLDR
Is there any elegant solution of following code:
var settings = new ConnectionSettings(new Uri("http://localhost:9200")).DefaultIndex("people");
var client = new ElasticClient(settings);
// this string represents incoming JSON message
string json = #"[
{
""id"": ""1"",
""name"": ""Andrej"",
""surname"": ""Burak"",
""dob"": ""1921-11-10T00:00:00+00:00""
},
{
""id"": ""2"",
""name"": ""Franta"",
""surname"": ""Dobrota"",
""dob"": ""1933-10-05T00:00:00+00:00""
},
{
""id"": ""3"",
""name"": ""Milos"",
""surname"": ""Ovcacek"",
""dob"": ""1988-05-05T00:00:00+00:00""
}
]";
JArray jArray = JArray.Parse(json);
foreach (var jtoken in jArray)
{
var jobj = (JObject)jtoken;
jobj.Add("DbCreated", JToken.FromObject(DateTime.UtcNow));
jobj.Add("DbCreatedBy", JToken.FromObject("authors name"));
}
//working, but seems to me a bit too clumsy to convert every item to string and then back to dynamic object
var converter = new ExpandoObjectConverter();
dynamic[] dlst = jArray.Select(t => (dynamic)JsonConvert.DeserializeObject<ExpandoObject>(t.ToString(), converter)).ToArray();
//not working cast
dynamic[] dlstNW = jArray.ToObject<dynamic[]>();
var indexManyResponse = client.IndexMany(dlst); //working partially (but not using ID as index)
var indexManyResponseNotWorking = client.IndexMany(jArray); //not working
var indexManyResponseNotWorking2 = client.IndexMany(dlstNW); //not working
// expected behavior
dynamic[] jsondyn = new dynamic[]
{
new { Id = "1", Name = "foo" },
new { Id = "2", Name = "bar" },
new { Id = "3", Name = "baz" },
};
var indexManyResponseWithIndex = client.IndexMany(jsondyn); //working perfectly, but don't know how to acieve this
After #gnud pointed me to low-level client I think I have found my answer. Important thing is to build request manually like it was explained here.
var settings = new ConnectionSettings(new Uri("http://localhost:9200")).DefaultIndex("people").DisableDirectStreaming();
var client = new ElasticClient(settings);
// this string represents incoming JSON message
string json = #"[
{
""id"": ""1"",
""name"": ""Andrej"",
""surname"": ""Burak"",
""dob"": ""1921-11-10T00:00:00+00:00""
},
{
""Id"": ""2"",
""name"": ""Franta"",
""surname"": ""Dobrota"",
""dob"": ""1933-10-05T00:00:00+00:00""
},
{
""Id"": ""3"",
""name"": ""Milos"",
""surname"": ""Ovcacek"",
""dob"": ""1988-05-05T00:00:00+00:00""
}
]";
JArray jArray = JArray.Parse(json);
// I have to build my request manually
List<string> esRequest = new List<string>();
foreach (var jtoken in jArray)
{
var jobj = (JObject)jtoken;
jobj.Add("DbCreated", JToken.FromObject(DateTime.UtcNow));
jobj.Add("DbCreatedBy", JToken.FromObject("authors name"));
string id;
// Ensure that Id is set even if we receive it in lowercase
if( jobj["Id"] == null)
{
if( jobj["id"] == null)
{
throw new Exception("missing Id");
}
id = jobj["id"].Value<string>();
}
else
{
id = jobj["Id"].Value<string>();
}
string indexName = "people";
esRequest.Add($"{{\"index\":{{\"_index\":\"{indexName}\",\"_id\":\"{id}\"}}}}");
esRequest.Add(jobj.ToString(Formatting.None));
}
PostData pd = PostData.MultiJson(esRequest);
var llres = client.LowLevel.Bulk<BulkResponse>(pd);
Hope it helps someone.
Input json
{
"ErrorMessage":"Transaction has been authorized successfully",
"ControlId":1000.00,
"Authorizations":[
{
"RMATranUUID":"1c1a88f7-d6cf-4ae8-87d3-ba06e9d9fe36",
"Payments":[
{
"PaymentNumber":"1",
"TotalPaymentsNumber":24,
"AmountDue":1000.0,
"AmountPaid":0.00
}
],
"Term":24,
"OTBReleaseAmount":null
},
{
"RMATranUUID":"b012ba9c-2dbd-4961-8959-ec0afbafbe13",
"OTBReleaseAmount":null
}
]
}
Output json, after parsing
JObject jsonPacket = JObject.Parse(inputString);
//Line no second changes 1000.00 to 1000.0
{
"ErrorMessage":"Transaction has been authorized successfully",
"ControlId":1000.0,
"Authorizations":[
{
"RMATranUUID":"1c1a88f7-d6cf-4ae8-87d3-ba06e9d9fe36",
"Payments":[
{
"PaymentNumber":"1",
"TotalPaymentsNumber":24,
"AmountDue":1000.0,
"AmountPaid":0.00
}
],
"Term":24,
"OTBReleaseAmount":null
},
{
"RMATranUUID":"b012ba9c-2dbd-4961-8959-ec0afbafbe13",
"OTBReleaseAmount":null
}
]
}
The reason is JObject at parsing doubles executes like this under the hood:
double data;
double.TryParse("1214.00", NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out data);
As data is of type double and double is floating point number, you didn't get the value you expect.
Either, you create a POCO and parse it into it, or you can do this:
string json = "{ \"ErrorMessage\":\"Transaction has been authorized successfully\", \"ControlId\":1000.00, \"Authorizations\":[ { \"RMATranUUID\":\"1c1a88f7-d6cf-4ae8-87d3-ba06e9d9fe36\", \"Payments\":[ { \"PaymentNumber\":\"1\", \"TotalPaymentsNumber\":24, \"AmountDue\":1000.0, \"AmountPaid\":0.00 } ], \"Term\":24, \"OTBReleaseAmount\":null }, { \"RMATranUUID\":\"b012ba9c-2dbd-4961-8959-ec0afbafbe13\", \"OTBReleaseAmount\":null } ]}";
var jsonReader = new JsonTextReader(new StringReader(json));
jsonReader.FloatParseHandling = FloatParseHandling.Decimal;
var jObject = JObject.Load(jsonReader);
Console.WriteLine(jObject["ControlId"].Value<decimal>()); // 1000.00
Console.WriteLine(jObject); // prints your actual json
I am using Json.Net to parse my JSON
This is my JSON:
"OptionType": {
"C": [
"C",
"Call"
],
"P": [
"P",
"Put"
]
}
Before this step, when processed, as a result, I would get a value from any of this.
For example Option Type: Call
Whatever value I get, I need it to be transcodified according to the above JSON.
Example: Option Type: C
First of all your sample data is not a valid JSON. You need to wrap it to the curvy braces.
If your sample is a part of the JSON object, you can map OptionType to the Dictionary<string, List<string>>.
To find valid option you will need to iterate this dictionary like in the sample below:
var valueToCheck = "Call";
string type = null;
foreach (var kvp in optionTypes)
{
if (kvp.Value.Contains(valueToCheck))
{
type = kvp.Key;
break;
}
}
Same using JObject with fixed JSON data:
var json = #"{
""OptionType"":{
""C"": [
""C"",
""Call""
],
""P"": [
""P"",
""Put""
]
}
}";
var valueToCheck = "Call";
string type = null;
var ot = JObject.Parse(json);
var objectType = ot["OptionType"];
foreach (var token in objectType)
{
var prop = token.ToObject<JProperty>();
var key = prop.Name;
var values = prop.Value.ToObject<List<string>>();
if (values.Contains(valueToCheck))
{
type = key;
break;
}
}
Code is not perfect but it is just an idea what to do.
You need to iterate over properties of JObject and then search your option type and then replace your search option with its parent key.
This is custom function can do above task.
public static JObject GetJObject(JObject jObject, List<string> optionType)
{
foreach (var type in jObject["OptionType"])
{
var key = type.ToObject<JProperty>().Name;
var values = type.ToObject<JProperty>().Value.ToObject<List<string>>();
foreach (var option in optionType)
{
if (values.Contains(option))
{
int index = values.IndexOf(option);
values.RemoveAt(index);
values.Insert(index, key);
}
}
JToken jToken = JToken.FromObject(values);
jObject.SelectToken("OptionType." + key).Replace(jToken);
}
return jObject;
}
And you can use above custom function this like
string json = File.ReadAllText(#"Path to your json file");
JObject jObject = JObject.Parse(json);
List<string> optionType = new List<string> { "Call", "Put" };
JObject result = GetJObject(jObject, optionType);
Output: