JSON: Serializing DataRow into parent object - json

I have a tree structure, created from rows in a DataTable, each DataRow generates one node.
Each node should of course hold the child nodes, but also the source DataRow:
public class TreeNode
{
public List<TreeNode> Items { get; internal set; }
public JToken RowJToken { get; internal set; }
}
(See the code at the end for the reason of RowJToken and not DataRow)
When I serialize the tree structure, I need each TreeNode to be serialized this way (this is dictated by the consuming client):
{
"items": [children],
"columnA": "ValueA",
"columnB": "ValueB",
"columnC": "ValueC"
}
where "[children]" represents the sub-nodes of the node (left out for brevity).
However, the result is instead:
{
"items": [children],
"rowJToken": {
"columnA": "ValueA",
"columnB": "ValueB",
"columnC": "ValueC"
}
}
Question: How can I serialize RowJToken content "on the same level" as the Items array, i.e. not being embedded in the "rowJToken" object?
The set of columns in the DataTable is not known beforehand (but I know the column names don't collide with "items").
Pieces of the code below. Note that the construction of the tree structure itself shouldn't be important in this case.
The reason I store RowJToken instead of DataRow in the TreeNode is that JSON.Net doesn't have a DataRow serializer, but only a DataTable serializer. Thus, I serialize the DataTable into a JArray first, then take the JToken that corresponds to the DataRow and stick that into the TreeNode.
public class TreeNode
{
public JToken RowJToken { get; internal set; }
public List<TreeNode> Items { get; internal set; }
}
...
List<TreeNode> treeNodes = GetTreeNodes(dataTable, childColumnName, parentColumnName);
DefaultContractResolver contractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy()
};
string json = JsonConvert.SerializeObject(treeNodes, new JsonSerializerSettings
{
ContractResolver = contractResolver,
Formatting = Formatting.Indented
});
...
private List<TreeNode> GetTreeNodes(DataTable dataTable, string childColumnName, string parentColumnName)
{
DataColumn childColumn = dataTable.Columns[childColumnName];
DataColumn parentColumn = dataTable.Columns[parentColumnName];
//
// Use the serializer for DataTable to generate a JSON array, containing each DataRow
//
var jArray = JArray.FromObject(dataTable, JsonSerializer.CreateDefault(new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }));
//
// The use of "NullObject" is merely to allow NULL values as the key to the dictionary
//
Dictionary<NullObject<string>, List<TreeNode>> hash = new Dictionary<NullObject<string>, List<TreeNode>>();
var rowIndex = 0;
foreach (DataRow r in dataTable.Rows)
{
string childId = r.Field<string>(childColumn);
string parentId = r.Field<string>(parentColumn);
if (!hash.TryGetValue(childId, out List<TreeNode> childIdTreeNodes))
{
childIdTreeNodes = new List<TreeNode>();
hash.Add(childId, childIdTreeNodes);
}
if (!hash.TryGetValue(parentId, out List<TreeNode> parentIdTreeNodes))
{
parentIdTreeNodes = new List<TreeNode>();
hash.Add(parentId, parentIdTreeNodes);
}
//
// Put the JToken which corresponds to this DataRow in the TreeNode
//
var rowJToken = jArray[rowIndex++];
parentIdTreeNodes.Add(new TreeNode()
{
Items = childIdTreeNodes,
RowJToken = rowJToken
});
}
//
// Return the root node(s), i.e. the nodes with NULL as parent ID
//
return hash[null];
}

You can get the output you want with a few simple changes.
In your TreeNode class, change the type of the RowJToken from JToken to JObject. (You might also want to change the property name to RowJObject as well, to be consistent, but that is not strictly necessary.)
public JObject RowJToken { get; internal set; }
Decorate the RowJToken property with a [JsonExtensionData] attribute.
[JsonExtensionData]
public JObject RowJToken { get; internal set; }
In your GetTreeNodes method, cast the JToken that is retrieved from the jArray to a JObject.
var rowJToken = (JObject)jArray[rowIndex++];
And that's it. When you serialize the treeNodes list, the RowJToken properties for each TreeNode will be on the same level as its respective Items array.
Proof of concept: https://dotnetfiddle.net/LEaeXo

Related

Asp .Net Core Json deserialize to model

Returns Json format data to me from an api.
However, the type of the "FuelType" property inside the object may be different. For example, 1 time comes as follows:
{
...
fuelType: "gasoline"
...
}}
But then it can happen:
{
...
fuelType: ["gasoline", "any"]
...
}}
If I set the "FuelType" property type on my model to a string, in the second case, Json will give me an error when it arrives, because it can't convert from array to string. No, if I set the type to an array, then, conversely, if a string arrives, it will issue an error because it cannot convert from a string to an array.
In this case, what should I put the "FuelType" property type in my model so that it does not make an error when deserializing?
It all depends on you,what type of value you want to receive?string or List?
I tried with the codes:
public class SomeModel
{
public SomeModel()
{
fuelType = new List<string>();
}
public int Id { get; set; }
public string Name { get; set; }
public List<string> fuelType { get; set; }
//you could move the codes to someservice
public List<string> someresult(string a)
{
var targetlist = new List<string>();
targetlist.Add(a);
return targetlist;
}
public List<string> someresult(List<string> a)
{
var targetlist = new List<string>();
targetlist.AddRange(a);
return targetlist;
}
}
[HttpPost]
public JsonResult SomeAction()
{
var somemodel = new SomeModel() { Id = 1, Name = "name" };
var somevalue = /*"a"*/new List<string>() { "a", "b", "c" };
var targetvalue = somemodel.someresult(somevalue);
somemodel.fuelType.AddRange(targetvalue);
return new JsonResult(somemodel);
}
Result:
try this
var fuelType = JsonConvert.DeserializeObject<MyClass>(json);
public class MyClass
{
[JsonProperty("fuelType")]
private JToken _fuelType;
[JsonIgnore]
public string[] fuelType
{
get {
if (_fuelType==null) return null;
return _fuelType is JArray ? _fuelType.ToObject<string[]>() : new string[] { (string)_fuelType }; }
set { _fuelType = JsonConvert.SerializeObject(value); }
}
}

How to use newtonsoft.Json to serialize and deserialize multiple objects into and out of a list

So I am practising using newtonsoft.Json by creating a very simple Register.
In this code a user enters a first name and last name and this is put into my very simple Person object, which is then put into a List
The user can add multiple people and they are put into the List
My code isn't working and I need help because I am learning.
It isn't working because the serialization and deserialization process is coded wrong I think.
At the beginning of the code a List is declared with the People from the json file
List<Person> AllPeopleAdded = new List<Person>(JsonConvert.DeserializeObject<List<Person>>(File.ReadAllText(jsonfilePath)));
This is the code showing how the people are added to the json file
File.AppendAllText(jsonfilePath,JsonConvert.SerializeObject(PeopleAddedThisTime));
This is the full code
using System;
using System.Collections.Generic;
using System.IO;
using OOP__Data_Entry__homework.Classes.Person;
using Newtonsoft.Json;
namespace OOP__Data_Entry__homework
{
class Program
{
const string FirstNameText = "Enter A First Name";
const string LastNameText = "Enter A Last Name";
const string ContinueText = "Would you Like to Add Another Person, Yes or No";
const string YesResponse = "Yes";
const string NoResponse = "No";
const string ContinueErrorText = "Enter Yes or No";
const string jsonfilePath = #"C:\OOP- Data Entry- homework\PeopleSaved.json";
static void Main(string[] args)
{
//done so that line 29 can work, without swaure brackets there is no list for the List (AllPeopleAdded) to take in
if(File.Exists(jsonfilePath))
{
}
else
{
File.WriteAllText(jsonfilePath,"[]");
}
List<Person> AllPeopleAdded = new List<Person>(JsonConvert.DeserializeObject<List<Person>>(File.ReadAllText(jsonfilePath)));
List<Person> PeopleAddedThisTime = new List<Person>();
//done so that the jsonfile(PeopleSaved.json) doesnt error after the new People are added when the user says they do not want to add any more people (line 57)
if(File.ReadAllText(jsonfilePath).StartsWith("[]"))
{
File.WriteAllText(jsonfilePath,"");
}
string FirstName;
string LastName;
while(true)
{
System.Console.WriteLine(FirstNameText);
FirstName=Console.ReadLine();
System.Console.WriteLine(LastNameText);
LastName = Console.ReadLine();
Person newPerson = new Person(FirstName,LastName);
PeopleAddedThisTime.Add(newPerson);
System.Console.WriteLine(ContinueText);
while(true)
{
string response = Console.ReadLine();
if (response==YesResponse)
{
break;
}
else if (response == NoResponse)
{
File.AppendAllText(jsonfilePath,JsonConvert.SerializeObject(PeopleAddedThisTime));
foreach(Person allPersons in AllPeopleAdded)
{
System.Console.WriteLine($"\n {allPersons.GetFullName()}");
}
foreach(Person newPersons in PeopleAddedThisTime)
{
System.Console.WriteLine($"\n {newPersons.GetFullName()}");
}
return;
}
else
{
System.Console.WriteLine(ContinueErrorText);
}
}
}
}
}
}
This is the json file after the code is run once
[{"mFirstName":"john ","mLastName":"doe"},{"mFirstName":"Josh","mLastName":"Smith"}]
This is the json file after the code is run again
(It is formatted wrong-that is a problem)
[{"mFirstName":"john ","mLastName":"doe"},{"mFirstName":"Josh","mLastName":"Smith"}][{"mFirstName":"Serge","mLastName":"Superhero"}]
The Person Class
using System;
namespace OOP__Data_Entry__homework.Classes.Person
{
class Person
{
public string mFirstName {get; private set; }
public string mLastName {get; private set; }
public Person(string firstName, string lastName)
{
mFirstName = firstName;
mLastName = lastName;
}
public string GetFullName()
{
return mFirstName+" "+mLastName;
}
}
}
What the json file looks after a couple times the code is run using serge's code
[{"mFirstName":null,"mLastName":null},{"mFirstName":null,"mLastName":null},{"mFirstName":"f","mLastName":"f"}]
You have to deserialize existing json into list, add new person to existing users lists ( or maybe remove some) and after this to serialize the whole list again. Using append will never work, since json always must have only one root element, and you have to insert a new data inside of this root. But you are trying to add the second root and that makes json invalid.
string json = string.Empty;
using (StreamReader r = new StreamReader(jsonfilePath))
json = r.ReadToEnd();
List<Person> AllPeopleAdded = JsonConvert.DeserializeObject<List<Person>>(json);
List<Person> PeopleAddedThisTime = new List<Person>();
//.....
AllPeopleAdded.AddRange(PeopleAddedThisTime);
using (StreamWriter file = File.CreateText(jsonfilePath))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(file, AllPeopleAdded);
}
// or serialize JSON to a string and then write string to a file
File.WriteAllText(jsonfilePath, JsonConvert.SerializeObject(AllPeopleAdded));
and fix person class
public class Person
{
public string mFirstName { get; private set; }
public string mLastName { get; private set; }
[JsonConstructor]
public Person(string mFirstName, string mLastName)
{
this.mFirstName = mFirstName;
this.mLastName = mLastName;
}
public string GetFullName()
{
return mFirstName + " " + mLastName;
}
}

UWP: Nested Custom Types with DataContractJsonSerializer

I am creating a nested custom type with primitive datatypes.
I am having a Web APi that returns the data in JSON.
using json2csharp.com, I am generating classes for the same.
I have decorated the primitive datatypes in all classes with DataMember and the types with DataContract.
I am using the following code for deserialization:
var resp = httpClient.GetAsync("http://ACTUAL_API_URI").Result;
var res = await resp.Content.ReadAsStringAsync();
var serializer = new DataContractJsonSerializer(typeof(RootObject));
byte[] byteArr= Encoding.ASCII.GetBytes(res);
var ms = new MemoryStream(byteArr);
var deserializedObj= (RootObject)serializer.ReadObject(ms);
I am not getting any exception. but the deserializedObj has null values for all the properties.
Any suggestions ?
You have a couple mistakes.
You returns collection of elements and try to deserialize one element instead collection
public IEnumerable<SampleData> Get()
{
return new SampleData[]
{
new SampleData()
{
Value = 100,
NestedTypeObject1 = new NestedType1()
{
ID = 101,
BD = "Description#1",
UD = "Description#2"
},
NestedTypeObject2 = new NestedType2()
{
Date = DateTime.Now.ToString(),
S1 = "S1 String",
S2 = "S2 String"
}
}
};
}
so you just change your code to
var serializer = new DataContractJsonSerializer(typeof(List<RootObject>));
var deserializedObj= (List<RootObject>)serializer.ReadObject(ms);
You have different model names between service side and client side, just you your SampleData model and all will be good. You must to rename MyNestedType1 to NestedTypeObject1 or add name to DataContractAttribute, e.g:
[DataContract(Name = "NestedTypeObject1")]
public class MyNestedType1
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string BD { get; set; }
[DataMember]
public string UD { get; set; }
}
It belongs for property names too.

How do I limit NewtonSoft.JSON to serialize an interface?

Very simple example (I use this in my unit tests):
private interface ISampleSubset
{
int id { get; }
}
private class Sample : ISampleSubset
{
public int id { get; set; }
public string name { get; set; }
}
Here's a small wrapper around NewtonSoft's JSON Serialize:
public string Serialize<T>(T t)
{
using (var sw = new StringWriter())
{
using (var jw = new JsonTextWriter(sw))
{
var js = JsonSerializer.Create();
js.Serialize(jw, t);
jw.Flush();
}
return sw.GetStringBuilder().ToString();
}
}
Now I want to serialize ISampleSubset:
And call it like so:
ISampleSubSet t = new Sample()
{
id = 1,
name = "joe"
};
string json = Serialize(t);
I expect to get
{"id":1}
but instead I get
{"id":1,"name":"joe"}
I'm guessing js.Serialize is using reflection to 'see' the other properties on the object 'outside' of the interface. How do I limit it to just those properties on the interface?
The serializer doesn't even know about your interface, so it's giving you everything - it accepts an object, so it doesn't know that you've declared your variable of type ISampleSubset - all it knows is the object itself is an instance of Sample.
Probably not the best solution, but you can use a JsonConverter to restrict the properties that appear in your serialized object.
This code is probably very inefficient - please don't judge - just threw it together, you can clean up the details and implement however you need:
public class MyConverter<T> : JsonConverter {
private readonly string[] _propertyNames;
public MyConverter() {
_propertyNames = typeof(T).GetProperties().Select(p => p.Name).ToArray();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
var objectType = value.GetType();
var newObject = new Dictionary<string, object>();
foreach (string propName in _propertyNames) {
var prop = objectType.GetProperty(propName);
if (prop != null) {
newObject[propName] = prop.GetValue(value, null);
}
}
string s = JsonConvert.SerializeObject(newObject);
writer.WriteRaw(s);
}
public override bool CanConvert(Type objectType) {
return true; // ?
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
public static string Serialize<T>(T t) {
return JsonConvert.SerializeObject(t, new MyConverter<T>());
}
Basically what it's doing is using reflection on the interface type to retrieve its properties, then creating a dictionary using only the properties found on the interface (you can do that a number of ways) - then using the simple JsonConvert class to serialize the dictionary.
NewtonSoft.JSON is serializing the object instance you created based on the default rule (as of .NET 3.5 IIRC) that all properties of an object are by default serializable. It doesn't matter if you declared your variable as an interface type because it's probably doing the serialization by reflection.
If you want to restrict the properties that get serialized the best way is to use theNonSerialized attribute.

Deserializing $ref and $id

So I'm trying to deserialize an object that has properties: $ref and $id. I have tried going between Dictionary as well as an object where I have specified namingconventions via JsonPropertyAttribute. Serialization works, but deserialization doesn't. The error I keep getting is:
Additional text found in JSON string
after finishing deserializing object.
Sample code where all three samples, fail.
[Serializable]
public class Ref
{
[JsonProperty(PropertyName = "$ref")]
public virtual string RefName { get; set; }
[JsonProperty(PropertyName = "$id")]
public virtual int Id { get; set; }
}
[Serializable]
public class Child
{
public virtual string Name { get; set; }
[JsonProperty(IsReference = true)]
public virtual Ref Father { get; set; }
}
class Program
{
static void Main(string[] args)
{
//Additional text found in JSON string after finishing deserializing object.
//Test 1
var reference = new Dictionary<string, object>();
reference.Add("$ref", "Persons");
reference.Add("$id", 1);
var child = new Dictionary<string, object>();
child.Add("_id", 2);
child.Add("Name", "Isabell");
child.Add("Father", reference);
var json = JsonConvert.SerializeObject(child);
var obj = JsonConvert.DeserializeObject<Dictionary<string, object>>(json); //Exception
//Test 2
var refOrg = new Ref {RefName = "Parents", Id = 1};
var refSer = JsonConvert.SerializeObject(refOrg);
var refDeser = JsonConvert.DeserializeObject<Ref>(refSer); //Exception
//Test 3
var childOrg = new Child {Father = refOrg, Name = "Isabell"};
var childSer = JsonConvert.SerializeObject(childOrg);
var childDeser = JsonConvert.DeserializeObject<Child>(refSer); //Exception
}
}
I have the same issue when trying to deserialize a swagger/json schema document. The solution is:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore;
You'll find that $ref & $id are special properties used by Json.NET to manage multiple instances of objects in a single object graph.
By putting these keys into your dictionary the deserialize process is trying to parse these as references to objects that don't exist.
Try altering the keys.
This answer worked for me: Json.Net adding $id to EF objects despite setting PreserveReferencesHandling to "None"
In your implementation of DefaultContractResolver/IContractResolver, add this;
public override JsonContract ResolveContract(Type type) {
var contract = base.ResolveContract(type);
contract.IsReference = false;
return contract;
}