I am using JsonForm to produce dynamic forms in my MVC core web application. I am using System.Text.Json.JsonSerializer.Serialize in the following code in my view model to produce a simple one field form. My aim is to store this json in the database eventually and retrieve it from there.
public TestsViewModel GetFormControls1()
{
var myJsonModel = new
{
schema = new
{
client = new
{
type = "object",
title = "Client",
properties = new
{
forename = new
{
type = "string",
title = "Forename",
minLength = 3,
maxLength = 10,
}
}
}
},
form = new List<Object>
{
new {
key = "client.forename",
title = "Forename"
}
},
value = new
{
client = new
{
forename = "",
}
}
};
TestsViewModel homeVm = new TestsViewModel();
homeVm.FormControls = System.Text.Json.JsonSerializer.Serialize(myJsonModel);
return homeVm;
}
Above code works fine and produces following json schema which is then used to create a form.
{
"schema": {
"client": {
"type": "object",
"title": "Client",
"properties": {
"forename": {
"type": "string",
"title": "Forename",
"minLength": 3,
"maxLength": 10
}
}
}
},
"form": [
{
"key": "client.forename",
"title": "Forename"
}
],
"value": {
"client": {
"forename": ""
}
}
}
I now need to produce an enum for my json so that a drop down for selecting a gender can appear in the form. However, I am unable to do that via c# code. Can someone help? I would like my c# code to produce following json (note two entries for gender in schema and form).
{
"schema": {
"client": {
"type": "object",
"title": "Client",
"properties": {
"forename": {
"type": "string",
"title": "Forename",
"minLength": 3,
"maxLength": 10
},
"gender": {
"type": "string",
"title": "Gender",
"enum": [
"male",
"female",
"alien"
]
}
}
}
},
"form": [
{
"key": "client.forename",
"title": "Forename"
},
{
"key": "client.gender",
"titleMap": {
"male": "Dude",
"female": "Dudette",
"alien": "I'm from outer space!"
}
}
],
"value": {
"client": {
"forename": ""
}
}
}
I have tried using following code but enum is a keyword in c# so I get error.
gender = new
{
type = "string",
title = "Gender",
enum = "[male, female, alien]"
}
Also Enum = "[male, female, alien]" produces "Enum": "[male, female, alien]" instead of "enum": [ "male", "female", "alien" ]
I have a gender lookup table which I will be eventually using to produce above enum somehow so any idea regarding that would be helpful as well.
UPDATE
#dbc's comment provides solution to most of my problem. However, I am still struglling with producing titleMap json if I try to map string to an int.
var gender3 = new
{
type = "string",
title = "Gender",
titleMap = new List<string> { new string("1" + ":" + "Male"), new string("2" + ":" + "Female")}
};
Above code produces
{
"type": "string",
"title": "Gender",
"titleMap": [
"1:Male",
"2:Female"
]
}
However, I need both 1 and Male in their own double quotations inside { } instead of [ ] as shown below.
{
"type": "string",
"title": "Gender",
"titleMap": {
"1": "Male",
"2": "Female"
}
}
enum is a keyword in C# so you need to prepend # to enum
gender = new
{
type = "string",
title = "Gender",
#enum = new string[3]{"male", "female", "alien"}
}
From what I understand, you are trying to serialize an array of enum values as their text names into JSON. The simplest solution for that is to add an array or list of enums property to your TestViewModel and use the JsonStringEnumConverter converter to serialize them as strings rather than numbers.
Here's an example of how you could achieve what you are looking for.
Let's say you have some enum with a set of values:
//This is an arbitrary enum, this could be 'Gender', in your case
public enum TestEnum
{
value1,
value2,
value3,
value4,
}
And you want to write down an array of those enum values. Add a list of enums property (or array of enums) to your model. If you want the property name within the JSON to be one of the reserved words (like enum) either override the name using the JsonPropertyName attribute (and keep the name whatever makes most sense programmatically):
public class TestsViewModel_Option1
{
// In your case, this property could be called 'Genders' to be self-documenting
[JsonPropertyName("enum")]
public List<TestEnum> ListOfEnums { get; set; }
}
OR, use the # symbol if you really want the property name to be the reserved keyword and don't want to/can't use the attribute for some reason.
public class TestsViewModel_Option2
{
// Or use fixed-size array, TestEnum[], if needed
public List<TestEnum> #enum { get; set; }
}
And now this is what your code looks like with the JsonSerializer:
private static void SerializeListOfEnums()
{
var model1 = new TestsViewModel_Option1
{
ListOfEnums = { TestEnum.value1, TestEnum.value3 }
};
var options = new JsonSerializerOptions
{
Converters = { new JsonStringEnumConverter() }
};
// {"enum":["value1","value3"]}
Console.WriteLine(JsonSerializer.Serialize(model1, options));
var model2 = new TestsViewModel_Option2
{
#enum = { TestEnum.value1, TestEnum.value3 }
};
// {"enum":["value1","value3"]}
Console.WriteLine(JsonSerializer.Serialize(model2, options));
}
Related
I've searched high and low for an exact example for how to populate a <select> control with <optgroup>, but I get various results. The documentation on select2.org states that the format should be like this:
{
"results": [
{
"text": "Group 1",
"children" : [
{
"id": 1,
"text": "Option 1.1"
},
{
"id": 2,
"text": "Option 1.2"
}
]
},
{
"text": "Group 2",
"children" : [
{
"id": 3,
"text": "Option 2.1"
},
{
"id": 4,
"text": "Option 2.2"
}
]
}
],
"pagination": {
"more": true
}
}
But my results throw an error stating that the 'ID' field is not present. I think it's due to the way I'm initializing the <select>, but I don't see another way.
First, my data is being pulled from a MS SQL db using a stored procedure. I don't have easy control over the data format, so I'm using Linq to create an object I then initialize the <select> with using .DataBind(). Here is the relevant code:
HTML:
<select id="MySelect" name="myselect" runat="server"></select>
Definitions for Parent and Child:
public class Parent : Child
{
public List<Child> children { get; set; }
}
public class Child
{
public string id { get; set; }
public string text { get; set; }
}
Linq statement:
var parents = myData
.Select(c => new Parent()
{
text = string.Format("{0} {1}", c.first_name, c.last_name),
id = c.ID.ToString(),
children = GetChildren(locations, c)
})
.ToArray();
// Convert to JSON
var json = JsonConvert.SerializeObject(new
{
results = parents
});
Linq result converted to JSON:
{
"results":[
{
"id":"2",
"text":"Parent 1 ",
"children":[
]
},
{
"id":"39",
"text":"Parent 2",
"children":[
{
"text":"Child 2.1",
"id":"62",
"Custom1":"196.00"
},
{
"text":"Child 2.2",
"id":"130",
"Custom1":"642.00"
},
{
"text":"Child 2.2",
"id":"61",
"Custom1":"0.00"
}
]
},
{
"id":"15",
"text":"Parent 3",
"children":[
{
"text":"Child 3.1",
"id":"13",
"Custom1":"40.00"
}
]
}
]
}
DataBind:
MySelect.DataSource = json;
MySelect.DataValueField = "id";
MySelect.DataTextField = "text";
MySelect.DataBind();
After all of this, the MySelect <select> renders with a dropdown containing only the parents and no <optgroup> elements in the actual HTML.
I have tried excluding the 'id' member from the <Parent> class, but then there is an error thrown from the .DataBind() that there is no 'id' even though the children all have ids.
I suspect that using .DataBind() isn't the way to handle this. I want to keep the code clean, short and quick.
How can I accomplish this?
I have a JSON that contains a list of types. These types have some name and some fields.
Fields have a property of dataType and if it's value is object, it refers to a another type. That type could be found by its name specified in referenceType property.
There is also a property named parentType which means the type is a child of the parentType and it contains some additional properties, but have to be considered as an object of parentType only.
I am trying to process this JSON to get all the nested property names for all the types present in the array.
{
"types": [
{
"name": "User1",
"fields": [
{
"name": "name",
"dataType": "string"
},
{
"name": "address",
"dataType": "object",
"referenceType": "Address",
"isArray": true
},
{
"name": "weeklyRoles",
"dataType": "object",
"isArray": true,
"referenceType": "Role"
}
]
},
{
"name": "User2",
"fields": [
{
"name": "name",
"dataType": "string"
},
{
"name": "address",
"dataType": "object",
"referenceType": "Address",
"isArray": true
}
]
},
{
"name": "Address",
"fields": [
{
"name": "AddressLine1",
"dataType": "string"
},
{
"name": "AddressLine2",
"dataType": "string"
}
]
},
{
"name": "BusinessAddress",
"parentType": "Address",
"fields": [
{
"name": "headquarters",
"dataType": "string"
}
]
},
{
"name": "ServiceAddress",
"parentType": "Address",
"fields": [
{
"name": "servicePartner",
"dataType": "string"
},
{
"name": "serviceType",
"dataType": "string"
}
]
},
{
"name": "Role",
"fields": [
{
"name": "roleName",
"dataType": "string"
},
{
"name": "accessCountsObj1",
"dataType": "object",
"referenceType": "Role2"
}
]
},
{
"name": "Role2",
"fields": [
{
"name": "roleName2",
"dataType": "string"
},
{
"name": "accessCountsObj2",
"dataType": "object",
"referenceType": "Role3"
}
]
},
{
"name": "Role3",
"fields": [
{
"name": "roleName3",
"dataType": "string"
},
{
"name": "accessCountsObj3",
"dataType": "object",
"referenceType": "Role4"
}
]
},
{
"name": "Role4",
"fields": [
{
"name": "roleName4",
"dataType": "string"
}
]
}
]
}
Pattern in which I am expecting the result is <typeName>;<fieldName>.<nestedFieldName>
Expected Output
[
"User1;address.AddressLine1",
"User1;address.AddressLine2",
"User1;address.headquarters",
"User1;address.servicePartner",
"User1;address.serviceType",
"User1;weeklyRoles.roleName",
"User1;weeklyRoles.accessCountsObj1.roleName2",
"User1;weeklyRoles.accessCountsObj1.accessCountsObj2.roleName3",
"User1;weeklyRoles.accessCountsObj1.accessCountsObj2.accessCountsObj3.roleName4",
"User2;address.AddressLine1",
"User2;address.AddressLine2",
"User2;address.headquarters",
"User2;address.servicePartner",
"User2;address.serviceType",
"Role;accessCountsObj1.roleName2",
"Role;accessCountsObj1.accessCountsObj2.roleName3",
"Role;accessCountsObj1.accessCountsObj2.accessCountsObj3.roleName4",
"Role2;accessCountsObj2.roleName3",
"Role2;accessCountsObj2.accessCountsObj3.roleName4",
]
I have tried to write a recursive function to process it but it is not giving me the expected result and also does not have terminating condition.
public IList<string> GetKeys(JArray types, string parentType = null)
{
var nestedKeys = new List<string>();
foreach (var type in types)
{
var fields = type[Constants.Fields].ToObject<List<JObject>>();
var typeName = type.Value<string>("name");
var nestedKeyBuilder = new StringBuilder($"{typeName};");
if (!string.IsNullOrEmpty(parentType))
{
nestedKeyBuilder = new StringBuilder($"{parentType};");
}
foreach (var field in fields)
{
var datatype = field.Value<string>("dataType");
if (string.Equals(datatype,"object"))
{
var fieldName = field.Value<string>("name");
var referenceTypeName = field.Value<string>("referenceType");
var referenceTypeObject = types.Where(t => string.Equals(t.Value<string>("name"), referenceTypeName))?.First();
if (referenceTypeObject != null)
{
var refTypeFields = referenceTypeObject["fields"].ToObject<List<JObject>>();
foreach (var refTypeField in refTypeFields)
{
var refTypeFieldName = refTypeField.Value<string>("name");
var refTypeDataType = refTypeField.Value<string>("dataType");
var refTypeReferenceTypeName = refTypeField.Value<string>("referenceType");
if (string.Equals(refTypeDataType, "object") && string.Equals(refTypeReferenceTypeName, currentReferenceType))
{
var refTypeNestedKeys = GetKeys(types, typeName);
nestedKeys.AddRange(refTypeNestedKeys);
}
else
{
nestedKeyBuilder.Append($"{fieldName}.{refTypeFieldName}");
nestedKeys.Add(nestedKeyBuilder.ToString());
}
}
}
}
}
}
return nestedKeys;
}
There is a NuGet that you can use to handle JSON files.
Search for Newtonsoft.Json
https://www.newtonsoft.com/json
All your problems should be solved with that
You can use Newtonsoft library to do this.
This is your POCO class.
{
public string name { get; set; }
public string dataType { get; set; }
public string referenceType { get; set; }
public bool? isArray { get; set; }
}
public class Type
{
public string name { get; set; }
public List<Field> fields { get; set; }
public string parentType { get; set; }
}
public class RootObject
{
public List<Type> types { get; set; }
}
and write the function to DeserializeObject
[TestMethod]
public void Read()
{
var sample1 = #"X:\JsonFilePath\data.json";
var jsonString=File.ReadAllText(sample1);
var result =JsonConvert.DeserializeObject<RootObject>(jsonString);
Assert.IsNotNull(result);
}
Use newtonsoft JSON to Deseriliaze the JSON string.
JSON.Deserialize<IEnumerable<Type>>(jsonString);
This should give you an IEnumerable to process it however you want. Types is the class you create with the JSON properties as class properties. You can use something like this
NOTE: Type and Field are very vague names. Type is a c# class and therefore you should not use it to create a class of your own. You will face ambiguous errors and you will need to qualify the namespace in full to use your class. Strongly suggesting to name it something else and use the attributes to map it to the JSON string.
[JsonObject(Title="People")]
public class Type
{
[JsonProperty("name")]
string Name{ get; set; }
[JsonProperty("fields")]
Field FieldValue[]{ get; set; }
}
"fields": [
{
"field": {
"name": "SMS",
"value": "Yes"
}
},
{
"field": {
"name": "Email",
"value": ""
}
},
{
"field": {
"name": "Total",
"value": ""
}
},
]
I have tried to form the JSON format like above, so i formed the class like below. While serialization it does not return expected form, how can i achieve this one.
public class Test
{
public List<Field> fields;
}
public class Field
{
public string name { get; set; }
public string value { get; set; }
}
Response:
"fields": [{
"name": "SMS",
"value": "Yes"
}, {
"name": "Email",
"value": ""
},{
"name": "Total",
"value": ""
}]
Use this website http://json2csharp.com and generate all the classes automatically. Just copy-paste your json there.
You can customize resulting JSON object with anonymous types and LINQ. Please try this code:
var test = new Test {fields = new List<Field>()};
test.fields.Add(new Field {name = "f1", value = "v1"});
test.fields.Add(new Field {name = "f2", value = "v2"});
var json = JObject.FromObject(new { fields = test.fields.Select(f => new {field = f}).ToArray() })
.ToString();
A json variable would be:
{
"fields": [
{
"field": {
"name": "f1",
"value": "v1"
}
},
{
"field": {
"name": "f2",
"value": "v2"
}
}
]
}
You just missed a class level:
public class Test
{
public List<FieldHolder> fields;
}
public class FieldHolder
{
public Field field { get; set; }
}
public class Field
{
public string name { get; set; }
public string value { get; set; }
}
My approximate data table structure which i will get from my sqlserver stored proc call. I am using .netframework 3.5 and i want to convert this datatable to json output using ado.net. i have stuck at GetCountryList(FK,TypeName). Kindly help me to acheive the below json output. Thanks for your help in advance.
Type ID Name FK
Continent 1 America 0
Continent 2 Asia 0
Continent 3 Africa 0
Country 11 USA 1
Country 12 China 2
Country 13 India 2
Country 14 Kenya 3
DataEntity
public class UserData
{
public string Type { get; set; }
public string ID { get; set; }
public string Name { get; set; }
public string FK {get; set;}
}
Service Method
public static UserData GetUserData() {
JavaScriptSerializer jSerializer = new JavaScriptSerializer ();
DataTable dtUserData = DataAccess.getUserDataTable();
if(dtUserData !=null && dtUserData.Rows.Count>0)
{
List<DataRow> list = dtMasterData.AsEnumerable().ToList();
List<UserData> lstContinent = new List<UserData>();
List<UserData> lstCountry = new List<UserData>();
foreach(DataRow dr in list)
{
var objUserData = new UserData();
objUserData.ID = dr["ID"].ToString();
objUserData.Type = dr["Type"].ToString();
objUserData.Name = dr["Name"].ToString();
objUserData.FK = dr["FK"].ToString();
if(objUserData.Type.ToString().ToLower=="continent")
{
lstContinent.Add(objUserData);
}
if(objUserData.Type.ToString().ToLower=="country")
{
if(dr["FK"] !=null)
{
var ForgnKey = dr["FK"].ToString();
var TypeName = dr["Type"].ToString();
var CountriesList = GetCountryList(FK,TypeName) //how do i call a generic method to filter out the country list as per passing Continent FK?
lstCountry.AddRange(CountriesList);
}
lstCountry.Add(objUserData);
}
private static List<T> GetCountryList (lstCountry,ForgnKey,TypeName) //Not sure with the syntax
{
var CountriesList = lstCountry.Where(p=>p.FK==ForgnKey).ToList();
}
}
return jSerializer.Serialize(objUserData);
}
Expected JSON Output :
"data": {
"Contnient":
[
{ "Id": "1", "Type": "Contient", "Name" :"America","FK":"1" },
{ "Id": "2", "Type": "Contient", "Name" :"Asia", "FK":"2" },
{ "Id": "3", "Type": "Contient", "Name" :"Africa", "FK":"2" },
{ "Id": "4", "Type": "Contient", "Name" :"Asia", "FK":"2" }
],
"America":
{
"Country":
[
{ "Id": "11", "Type": "Country","Name":"India","FK":"1" }
]
},
"Asia:
{
"Country":
[ { "Id": "12", "Type": "Country","Name":"China","FK":"2" },
{ "Id": "13", "Type": "Country","Name":"India","FK":"2" }
]
}
"Africa":
{
"Country":
[ { "Id": "14", "Type": "Country","Name":"Kenya","FK":"3" }
]
}
Shouldn't the generic function be defined as:
private static List<T> GetCountryList<T>(lstCountry,ForgnKey,TypeName)
{
return lstCountry.Where(p=>p.FK==ForgnKey).ToList();
}
Then called as:
var CountriesList = GetCountryList<UserData>(lstCountry,FK,TypeName);
I have the following json coming through the elasticsearch:
{
"_index": "data-2016-01-14",
"_type": "type-data",
"_id": "AVJBBNG-TE8FYIA1rf1p",
"_score": 1,
"_source": {
"#message": {
"timestamp": 1452789770326461200,
"eventID": 1452789770326461200,
"eventName": "New",
"Price": "38.34",
"Qty": 100,
"statistic_LatencyValue_ns": 1142470,
"statistic_LatencyViolation": false,
"statistic_LossViolation": false
},
"#timestamp": "2016-01-14T16:42:50.326Z",
"#fields": {
"timestamp": "1452789770326"
}
},
"fields": {
"#timestamp": [
1452789770326
]
}
}
I'm using Nest to try to get the eventName data i created the class and marked the property:
public class ElasticTest
{
[ElasticProperty(Type = FieldType.Nested)]
public string eventName { get; set; }
}
But the following query is returning 0 results, what am i doing wrong?
var result = client.Search<CorvilTest>(s => s
.From(0)
.Size(10000)
.Query(x => x
.Term(e => e.eventName,"New"))
);
var r = result.Documents;
Mapping definition:
{
"data-2016-01-14": {
"mappings": {
"type-data": {
"properties": {
"#fields": {
"properties": {
"timestamp": {
"type": "string"
}
}
},
"#message": {
"properties": {
"OrderQty": {
"type": "long"
},
"Price": {
"type": "string"
},
"eventID": {
"type": "long"
},
"eventName": {
"type": "string"
},
"statistic_LatencyValue_ns": {
"type": "long"
},
"statistic_LatencyViolation": {
"type": "boolean"
},
"statistic_LossViolation": {
"type": "boolean"
},
"timestamp": {
"type": "long"
}
}
},
"#timestamp": {
"type": "date",
"format": "dateOptionalTime"
}
}
}
}
}
}
I see that the field #message.eventName is using a standard analyzer which means that its value is lower-cased and split at word boundaries before indexing. Hence the value "new" is indexed and not "New". Read more about it here. You need to be mindful about this fact when using a Term Query. Another thing is that the field eventName is not of nested type. So the code below should work for you.
var result = client.Search<CorvilTest>(s => s
.From(0)
.Size(10000)
.Query(x => x
.Term(e => e.Message.EventName, "new"))); // Notice I've used "new" and not "New"
var r = result.Documents;
For the above code to work the definition of CorvilTest class should be something like below:
public class CorvilTest
{
[ElasticProperty(Name = "#message")]
public Message Message { get; set; }
/* Other properties if any */
}
public class Message
{
[ElasticProperty(Name = "eventName")]
public string EventName { get; set; }
}