json properties to lower case c# - c#

I'm getting from client json string:
{ "Client": { "Name": "John" } }
but for the further handling I need the following json:
{ "client": { "name": "John" } }
I tried something like that, but it didn't help:
public class LowerCaseNamingStrategy : NamingStrategy
{
protected override string ResolvePropertyName(string name)
{
return name.ToLower();
}
}
and
var settings = new JsonSerializerSettings();
settings.ContractResolver = new DefaultContractResolver { NamingStrategy = new LowerCaseNamingStrategy() };
var json = JsonConvert.DeserializeObject(input.DataJson, settings);
JSON is dynamic object, so I don't know properties are there.
How can I do that with c#? With using Newtonsoft.Json or may be with using Xml.

If I understood you correctly, you need to modify properties in your Json string, but not convert the Json into object.
In this case you can try to parse Json into JObject and replace properties in that object.
private static void ChangePropertiesToLowerCase(JObject jsonObject)
{
foreach (var property in jsonObject.Properties().ToList())
{
if(property.Value.Type == JTokenType.Object)// replace property names in child object
ChangePropertiesToLowerCase((JObject)property.Value);
property.Replace(new JProperty(property.Name.ToLower(),property.Value));// properties are read-only, so we have to replace them
}
}
sample:
var jsonString = #"{ ""Client"": { ""Name"": ""John"" } }";
var jobj = JObject.Parse(jsonString, new JsonLoadSettings());
ChangePropertiesToLowerCase(jobj);
var stringWithLowerCaseProperties = jobj.ToString(Formatting.None);

Try LowercaseContractResolver instead
var settings = new JsonSerializerSettings();
settings.ContractResolver = new LowercaseContractResolver();
var json = JsonConvert.DeserializeObject(input.DataJson, settings);

Extending Anton Semenov answer for cases when JSON can contain an Array of Objects:
private static void ChangePropertiesToLowerCase(JObject jsonObject)
{
foreach (var property in jsonObject.Properties().ToList())
{
if (property.Value.Type == JTokenType.Object) // replace property names in child object
ChangePropertiesToLowerCase((JObject)property.Value);
if (property.Value.Type == JTokenType.Array)
{
var arr = JArray.Parse(property.Value.ToString());
foreach (var pr in arr)
{
ChangePropertiesToLowerCase((JObject)pr);
}
property.Value = arr;
}
property.Replace(new JProperty(property.Name.ToLower(CultureInfo.InvariantCulture), property.Value)); // properties are read-only, so we have to replace them
}
}

All other solutions here modify the original object. Here's an immutable version which returns a new object with lowercase properties:
public static class JsonExtensions
{
public static JToken ToLowerRecursive(this JToken token)
{
if (token is JObject jobj)
return new JObject(jobj.Properties().Select(x => new JProperty(x.Name.ToLowerInvariant(), x.Value.ToLowerRecursive())));
if (token is JArray jarr)
return new JArray(jarr.Select(x => x.ToLowerRecursive()));
return token;
}
}

This is easy way without Regex. Replace every [{ "A] with [{ "a]
var json = "{ \"Client\": { \"Name\": \"John\" } }";
var newJson = string.Empty;
foreach (var w in json.Split(new[] { "{ \"" }, StringSplitOptions.RemoveEmptyEntries))
{
if (w[0] != null)
{
newJson += "{ \"" + (w[0].ToString().ToLower()) + w.Remove(0,1);
}
}
result:
"{ \"client\": { \"name\": \"John\" } }"

Related

How would you add a JArray into a JObject without adding a new JObject key/name?

using .NET C#, I am trying to create a new JObject from a JArray. I have a FetchData JObject that I want to return a JObject of data for data driven testing. Here is what I have so far:
public static JObject FetchData(string testMethodName)
{
using (StreamReader r = new StreamReader("PathToJsonfile"))
{
string jsonstring = r.ReadToEnd();
JObject obj = JObject.Parse(jsonstring);
JArray jsonArray = JArray.Parse(obj[testMethodName].ToString());
JObject jObject = new JObject(new JProperty("test",jsonArray));
return jObject;
}
}
I want to return a JObject of test data that pertains to the testMethod which is being run. when I run this code, jObject returns:
"test": [
{
"loginId": "testuser1",
"userCase": "verify for user"
},
{
"loginId": "testuser2",
"userCase": "verify for user"
}
]
My issue is that I only want to return the following arrays within the JObject:
{"loginId":"testuser1","userCase":"verify for user"}
I have researched for a while and cannot find a solution without adding a key to the new JObject, in this case, key being "test".
Is this even possible in C#?
I have also tried adding the JArray directly to the JObject:
JObject jObject = new JObject(new JObject(jsonArray));
but get error : System.ArgumentException: 'Can not add Newtonsoft.Json.Linq.JArray to Newtonsoft.json.Linq.JObject
I have also tried adding the arrays to the JObject like this:
for (int i = 0; i < jsonArray.Count; i++)
{
jObject[i] = jsonArray[i];
}
but get error : System.ArgumentException : Set JObject values with invalid key value: 0. Object property name expected.
fwiw this is how I am doing this is Java and it works like a charm, but I cannot figure it out in C#. Java Code:
JSONObject[] jsonObject = new JSONObject[jsonArray.length()];
for (int i = 0; i < jsonArray.length(); i++) {
jsonObject[i] = jsonArray.getJSONObject(i);
}
To get first element of array you can do next:
var json = #"{
""test"": [
{
""loginId"": ""testuser1"",
""userCase"": ""verify for user""
},
{
""loginId"": ""testuser2"",
""userCase"": ""verify for user""
}]
}";
var testMethodName = "test";
var jObject = JObject.Parse(json);
var first = (JObject)jObject[testMethodName][0]; // note that this can throw for multiple reasons
Analog of your java code would be:
// parse jObject as earlier
var jArr = (JArray)jObject[testMethodName];
var jsonObjectArr = new JObject[jArr.Count];
for (int i = 0; i < jArr.Count; i++)
{
jsonObjectArr[i] = (JObject)jArr[i];
}
Or you can use LINQ to JSON:
// parse jObject as earlier
var jsonObjectArr = jObject[testMethodName]
.Children<JObject>()
.ToArray();
Why don't you return only the first object of your array like this:
public static JToken FetchOneDatum(string testMethodName)
{
string jsonString = GetFileContent("sampleTest.txt");
JObject obj = JObject.Parse(jsonString);
JArray jsonArray = JArray.Parse(obj[testMethodName].ToString());
return jsonArray[0];
}
You can find the whole visual Studio solution here:
solution on GitHub
C# approach ;)
public class Test
{
public string loginId { get; set; }
public string userCase { get; set; }
}
public class Scenario
{
public Test[] tests { get; set; }
}
// Usage
public static Test FetchData(string testMethodName)
{
using (var reader = new StreamReader("PathToJsonfile"))
{
var json = reader.ReadToEnd();
var scenario = JsonConvert.DeserializeObject<Scenario>(json);
return scenario.tests.First();
}
}

Merge Sparse Data into Dictionary using Json.NET PopulateObject

I would like to load sparse data in JSON format to get a result with missing data filled in with defaults, but my defaults include predefined instances of an extensible set rather than just fixed fields.
For (arbitrary) example,
Types
class Link
{
public string Addr;
public short Port;
public Link() { Addr = "0.0.0.0"; Port = 80; }
public override string ToString() { return Addr + ":" + Port.ToString(); }
}
class Targets
{
public Link Fixed;
public Dictionary<string, Link> Variable;
public Targets()
{
Fixed = new Link() { Addr = "192.168.0.1" };
Variable = new Dictionary<string, Link>
{
["Common"] = new Link() { Addr = "192.168.0.2" }
};
}
public override string ToString()
{
var result = new System.Text.StringBuilder();
result.Append("Fixed").Append('=').Append(Fixed)
.Append(' ');
foreach (var link in Variable)
{
result.Append(link.Key).Append('=').Append(link.Value)
.Append(' ');
}
return result.ToString();
}
}
Usage
var targets = new Targets();
string json = #"{
'Fixed': { 'Port':12345 },
'Variable': {
'Common': { 'Port':12345 }
}
}";
Newtonsoft.Json.JsonConvert.PopulateObject(json, targets);
Console.WriteLine(targets);
Outputs Fixed=192.168.0.1:12345 Common=0.0.0.0:12345 rather than the desired Fixed=192.168.0.1:12345 Common=192.168.0.2:12345.
This shows that the desired merge logic works for fixed properties, but not for items in a Dictionary despite the fact that the Dictionary will otherwise serialize/deserialize just like a type with fixed properties.
Took me a while to figure this out. Json.NET has a dedicated function for merging two JObjects together. Here's your example modified to use this method:
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
namespace ConsoleApp3
{
class Link
{
public string Addr;
public short Port;
public Link() { Addr = "0.0.0.0"; Port = 80; }
public override string ToString() { return Addr + ":" + Port.ToString(); }
}
class Targets
{
public Link Fixed;
public Dictionary<string, Link> Variable;
public Targets()
{
Fixed = new Link() { Addr = "192.168.0.1" };
Variable = new Dictionary<string, Link>
{
["Common"] = new Link() { Addr = "192.168.0.2" },
["Common2"] = new Link() { Addr = "192.168.0.25" }
};
}
public override string ToString()
{
var result = new System.Text.StringBuilder();
result.Append("Fixed").Append('=').Append(Fixed)
.Append(' ');
foreach (var link in Variable)
{
if (link.Key != "Variable")
result.Append(link.Key).Append('=').Append(link.Value)
.Append(' ');
}
return result.ToString();
}
}
class Program
{
static void Main(string[] args)
{
var targets = new Targets();
JObject o1 = JObject.Parse( #"{
'Fixed': { 'Port':12345 },
'Variable': {
'Common': { 'Port':12345 }
}
}");
JObject o2 = JObject.FromObject(targets);
o2.Merge(o1, new JsonMergeSettings
{
// union array values together to avoid duplicates
MergeArrayHandling = MergeArrayHandling.Union
});
string json = o2.ToString();
Console.WriteLine(json);
JsonConvert.PopulateObject(json, targets);
Console.WriteLine(targets);
Console.ReadKey();
}
}
}
The output is:
{
"Fixed": {
"Addr": "192.168.0.1",
"Port": 12345
},
"Variable": {
"Common": {
"Addr": "192.168.0.2",
"Port": 12345
},
"Common2": {
"Addr": "192.168.0.25",
"Port": 80
}
}
}
Fixed=192.168.0.1:12345 Common=192.168.0.2:12345 Common2=192.168.0.25:80
EDIT by OP: Refined into extension methods without extra ToString/deserialization:
static class SerializerExtensions
{
public static T MergeObject<T>(this JsonSerializer serializer, JsonReader json, T target)
{
JObject o1 = JObject.FromObject(target, serializer);
JObject o2 = serializer.Deserialize(json) as JObject;
o1.Merge(o2, new JsonMergeSettings
{ // union array values together to avoid duplicates
MergeArrayHandling = MergeArrayHandling.Union,
// an explicit null removes an existing item
MergeNullValueHandling = MergeNullValueHandling.Merge,
});
serializer.Populate(o1.CreateReader(), target);
return target;
}
public static T MergeObject<T>(this JsonSerializer serializer, JsonReader json, JObject template)
{
JObject o1 = template.DeepClone() as JObject;
JObject o2 = serializer.Deserialize(json) as JObject;
o1.Merge(o2, new JsonMergeSettings
{ // union array values together to avoid duplicates
MergeArrayHandling = MergeArrayHandling.Union,
// an explicit null removes an existing item
MergeNullValueHandling = MergeNullValueHandling.Merge,
});
return serializer.Deserialize<T>(o1.CreateReader());
}
}

Remove "Integer" properties from JSON Object?

JSON Object:
{
"Footer": "footer",
"RowType": 4,
"answers": [
{
"answer": 1,
"FooterInner": "innerfooter"
},
{
"answer": 2,
"FooterInner": "innerfooter2"
}
]
}
I need to remove all "integer" properties from JSON. JSON object may differ every time. So, consider that we do not know property key names.
Expected JSON Object:
{
"Footer": "",
"answers": [
{
"FooterInner": "innerfooter"
},
{
"FooterInner": "innerfooter2"
}
]
}
The above JSON object is just an example. JSON objects may differ every time (the user uploads JSON objects from UI) and I don't know the hierarchy and key/property names in JSON in advance. And JSON may contain N-nested properties.
I tried a lot things, but couldn't achieve the solution. Is there any way to do it?
You'll have to adjust (and CHECK) the RegEx for your needs but this method will strip out the integers from the json string.
private string CleanJson(string json)
{
var regEx = new Regex("(\"\\w*\": \\d*,)");
var jsonWithoutIntegers = regEx.Replace(json, string.Empty);
return jsonWithoutIntegers;
}
A cleaner way may be writing it up as an extension method.
public static class Extensions
{
public static JToken RemoveFieldTypes(this JToken token,params JTokenType []fieldTypes)
{
JContainer container = token as JContainer;
if (container == null) return token;
var tokensToRemove = new List<JToken>();
foreach (JToken el in container.Children())
{
JProperty p = el as JProperty;
if(p!=null && fieldTypes.Contains(p.Value.Type))
{
tokensToRemove.Add(el);
}
el.RemoveFieldTypes(fieldTypes);
}
foreach (JToken el in tokensToRemove)
{
el.Remove();
}
return token;
}
}
Now you can do the following.
JToken nodeList = JToken.Parse(strJson);
nodeList.RemoveFieldTypes(JTokenType.Integer);
Here is the solution:
static void Main(string[] args)
{
var json =
#"{
""Footer"": ""footer"",
""RowType"": 4,
""answers"":
[
{
""answer"": 1,
""FooterInner"": ""innerfooter""
},
{
""answer"": 2,
""FooterInner"": ""innerfooter2""
}
]
}";
JToken nodeList = JToken.Parse(json);
List<JTokenType> typesToRemove = new List<JTokenType>(){JTokenType.Boolean, JTokenType.Float, JTokenType.Integer};
removeFields(nodeList, typesToRemove);
Console.WriteLine(nodeList.ToString());
Console.ReadKey();
}
private static void removeFields(JToken token, List<JTokenType> typesToRemove)
{
JContainer container = token as JContainer;
if (container == null) return;
List<JToken> removeList = new List<JToken>();
foreach (JToken el in container.Children())
{
JProperty p = el as JProperty;
if (p != null && typesToRemove.Contains(p.Value.Type))
{
removeList.Add(el);
}
removeFields(el, typesToRemove);
}
foreach (JToken el in removeList)
{
el.Remove();
}
}
I am just sharing my first working code, and also for extension method solution you can use #Anu Visman's answer(I didn't try it).

NewtonSoft json parsing

Can somebody help me to parse the json and get the details.
Lets say i have Top2 input parameter as Police and i want to know the respective Top3 and in that Top3 array i need to check whether Test1Child value is there or not.
I am using newtonsoft json + c# and so far i can get till DeviceTypes using below code
var json = File.ReadAllText(jsonFile); // below json is stored in file jsonFile
var jObject = JObject.Parse(json);
JArray MappingArray =(JArray)jObject["Top1"];
string strTop1 = ObjZone.Top1.ToString();
string strTop2 = ObjZone.Top2.ToString();
var JToken = MappingArray.Where(obj => obj["Top2"].Value<string>() == strTop2).ToList();
//Json
{
"Top1": [
{
"Top2": "Test1",
"Top3": [
"Test1Child"
]
},
{
"Top2": "Test2",
"Top3": [
"Test2Child"
]
}
]
}
I'd use http://json2csharp.com/ (or any other json to c# parser) and then use C# objects (it's just easier for me)
This would look like that for this case:
namespace jsonTests
{
public class DeviceTypeWithResponseTypeMapper
{
public string DeviceType { get; set; }
public List<string> ResponseTypes { get; set; }
}
public class RootObject
{
public List<DeviceTypeWithResponseTypeMapper> DeviceTypeWithResponseTypeMapper { get; set; }
}
}
and the use it like that in code:
var rootob = Newtonsoft.Json.JsonConvert.DeserializeObject<RootObject>(str);
var thoseThatHaveNotUsed = rootob.DeviceTypeWithResponseTypeMapper.Where(dtwrtm =>
dtwrtm.ResponseTypes.Any(rt => rt == "NotUsed"));
foreach (var one in thoseThatHaveNotUsed)
{
Console.WriteLine(one.DeviceType);
}
this code lists all the Device types that have "NotUsed" among the responses.
version 2 (extending your code) would look like that, i believe:
static void Main(string[] args)
{
var json = str; // below json is stored in file jsonFile
var jObject = JObject.Parse(json);
JArray ZoneMappingArray = (JArray)jObject["DeviceTypeWithResponseTypeMapper"];
string strDeviceType = "Police";
string strResponseType = "NotUsed";
var JToken = ZoneMappingArray.Where(obj => obj["DeviceType"].Value<string>() == strDeviceType).ToList();
var isrespTypeThere = JToken[0].Last().Values().Any(x => x.Value<string>() == strResponseType);
Console.WriteLine($"Does {strDeviceType} have response type with value {strResponseType}? {yesorno(isrespTypeThere)}");
}
private static object yesorno(bool isrespTypeThere)
{
if (isrespTypeThere)
{
return "yes!";
}
else
{
return "no :(";
}
}
result:
and if you'd like to list all devices that have response type equal to wanted you can use this code:
var allWithResponseType = ZoneMappingArray.Where(jt => jt.Last().Values().Any(x => x.Value<string>() == strResponseType));
foreach (var item in allWithResponseType)
{
Console.WriteLine(item["DeviceType"].Value<string>());
}

How to deserialise a JSON object where the object structure is not known

Part of my code serializes file paths of a machine into JSON in the below format. I am struggling to take this JSON and put the file paths back together again. I am using Newtonsoft JSON lib; I find it's excellent for building JSON. As you can see, my JSON has nested objects.
The JSON I have:
{
".": {
"proc": {
"15": {
"task": {
"15": {
"exe": {},
"mounts": {
"list_of_files": [
"mounts.xml"
]
},
"mountinfo": {
"list_of_files": [
"mountinfo.xml"
]
},
"clear_refs": {
"list_of_files": [
"clear_ref.xml"
]
}
}
}
},
"14": {
"loginuid": {
"list_of_files": [
"loginuid.xml"
]
},
"sessionid": {
"list_of_files": [
"sessionid.xml"
]
},
"coredump_filter": {
"list_of_files": [
"coredump_filter.xml"
]
},
"io": {
"list_of_files": [
"io.xml"
]
}
}
}
}
}
The array I want to generate from this.
string[] dirArray = {
"./proc/15/task/15/exe",
"./proc/15/task/15/mounts/mounts.xml",
"./proc/15/task/15/mountinfo/mountinfo.xml",
"./proc/15/task/15/clear_refs/clear_ref.xml",
"./proc/14/loginuid/loginuid.xml",
"./proc/14/sessionid/sessionid.xml",
"./proc/14/coredump_filter/coredump_filter.xml",
"./proc/14/io/io.xml"
}
My efforts so far-- I deserialised the JSON into a dynamic variable but I'm not sure how to handle two issues:
My JSON format is unknown, I don't know how deep the objects go, how can I handle this?
How do I work with dynamic variables when they are defined at run-time?
EDIT
Sorry, my original JSON format was wrong, so it doesn't work with the answer provided by user12864. I'm getting an error: Unable to cast object of type 'Newtonsoft.Json.Linq.JArray' to type 'Newtonsoft.Json.Linq.JObject'.
Here is a fiddle showing where I'm at so far.
#user12864 has the right idea in his answer, but the code needs to be adjusted to account for the fact that each directory can have an array of files rather a single "file" object (you really should have mentioned that in your question originally). Here is an updated method to handle that:
private static void AddToFileList(JObject jo, List<string> list, string prefix)
{
foreach (var kvp in jo)
{
if (kvp.Key == "list_of_files")
{
foreach (string name in (JArray)kvp.Value)
{
list.Add(prefix + name);
}
}
else
{
JObject dir = (JObject)kvp.Value;
if (dir.Count == 0)
{
list.Add(prefix + kvp.Key);
}
else
{
AddToFileList(dir, list, prefix + kvp.Key + "/");
}
}
}
}
Full demo:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
class Program
{
static void Main(string[] args)
{
string json = #"
{
""."": {
""proc"": {
""15"": {
""task"": {
""15"": {
""exe"": {},
""mounts"": {
""list_of_files"": [
""mounts.xml""
]
},
""mountinfo"": {
""list_of_files"": [
""mountinfo.xml""
]
},
""clear_refs"": {
""list_of_files"": [
""clear_ref.xml""
]
}
}
}
},
""14"": {
""loginuid"": {
""list_of_files"": [
""loginuid.xml""
]
},
""sessionid"": {
""list_of_files"": [
""sessionid.xml""
]
},
""coredump_filter"": {
""list_of_files"": [
""coredump_filter.xml""
]
},
""io"": {
""list_of_files"": [
""io.xml""
]
}
}
}
}
}";
JObject jo = JObject.Parse(json);
foreach (string path in CreateFileList(jo))
{
Console.WriteLine(path);
}
}
private static List<string> CreateFileList(JObject jo)
{
List<string> ret = new List<string>();
AddToFileList(jo, ret, "");
return ret;
}
private static void AddToFileList(JObject jo, List<string> list, string prefix)
{
foreach (var kvp in jo)
{
if (kvp.Key == "list_of_files")
{
foreach (string name in (JArray)kvp.Value)
{
list.Add(prefix + name);
}
}
else
{
JObject dir = (JObject)kvp.Value;
if (dir.Count == 0)
{
list.Add(prefix + kvp.Key);
}
else
{
AddToFileList(dir, list, prefix + kvp.Key + "/");
}
}
}
}
}
Output:
./proc/15/task/15/exe
./proc/15/task/15/mounts/mounts.xml
./proc/15/task/15/mountinfo/mountinfo.xml
./proc/15/task/15/clear_refs/clear_ref.xml
./proc/14/loginuid/loginuid.xml
./proc/14/sessionid/sessionid.xml
./proc/14/coredump_filter/coredump_filter.xml
./proc/14/io/io.xml
Fiddle: https://dotnetfiddle.net/r8CkI2
This should give exactly what you're looking for; just create a JObject with JObject.Parse and pass it to CreateFileList. It won't handle malformed JSON in any nice way.
static List<string> CreateFileList(JObject j)
{
List<string> ret = new List<string>();
AddToFileList(j, ret, "");
return ret;
}
static void AddToFileList(JObject j, List<string> dest, string prefix)
{
if (prefix.Length != 0)
prefix = prefix + '/';
foreach (var kvp in j)
{
var jnext = (JObject)kvp.Value;
if (kvp.Key == "file")
dest.Add(prefix + (string)jnext["name"]);
else
AddToFileList(jnext, dest, prefix + kvp.Key);
}
}
Fiddle at https://dotnetfiddle.net/dQQ4tI
Update:
Here is a revised answer, after you clarified your requirement of:
The JavaScript Object Notation is built on the server, edited by user through a hierarchical tree interface component. That can be crawled incredibly easy.
So in essence your utilizing a component, in which your hoping to build simple JavaScript Object Notation derived from the component. Your User Interface will be unknown, so I'll make some presumptions.
Build our Object:
public class XmlPath
{
public string Location { get; set; }
}
The XmlPath will represent our object. Which will be basic auto property.
Add Content to our Object:
private List<XmlPath> AddXmlPath(List<string> location)
{
List<XmlPath> content = new List<XmlPath>();
foreach(string item in location)
content.Add(new XmlPath() { Location = item });
return content;
}
This will be incredibly simple method, it will take a large List<string> of your user data and add them to your XmlPath object.
Remove content from our Object:
private List<XmlPath> RemoveXmlPath(List<XmlPath> root, string location)
{
root.Remove(new XmlPath() { Location = location });
return root;
}
Those two methods truly don't need to be, I'm just demonstrating and showing how you could. Plus it will outline the intentions a bit easier for you to implement. Please note this is incredibly crude approach.
Serialize / Deserialize Our Object to JavaScript Objection Notation:
JavaScriptSerializer serializer = new JavaScriptSerializer();
var xmlPath = AddXmlPath(List<string> location);
var result = serializer.Serialize(xmlPath);
var deserialize = serializer.Deserialize(List<XmlPath>>(result);
Our content is exposed now through a basic loop:
foreach(XmlPath item in deserialize)
{
// Exposed Model via 'item.Location'
}
You'll simply need to correlate this core functionality to your implementation. The approach is crude, quite rudimentary, definitely will need to be improved on for production. However this should get you started with:
Serialize data on the server.
Deserialize server data.
Manipulating the object.
Hope this is better for you.

Categories

Resources