I've been searching the internet for literally hours trying to find a very simple example of serialization and deserialization with a JSON call in C#. After careful browsing and piecing together, I'd like to call a JSON (POST & GET) function in my webservice (see below).
Here's what I was able to piece together
This would be my service contract (IExecWebservice.svc)
using System.ServiceModel;
using System.ServiceModel.Web;
namespace _27963199
{
[ServiceContract]
public interface IExecFunction
{
[WebGet(UriTemplate = "/{function}/{args}")]
double CalcThis(string function, string args);
}
}
In my main code I parse out the users request URI (IExecFunction.cs)
//user will send variables in REST URI http://myCalcServer/CalcThis/MethodA/10,20
using FunctionLibrary;
using System;
using System.Reflection;
namespace _27963199
{
public class ExecFunctionService : IExecFunction
{
public double CalcThis(string function, string args)
{
Type t = typeof(Functions);
MethodInfo[] libraryFunctions = t.GetMethods(BindingFlags.Static | BindingFlags.Public);
string[] arguments = args.Split(',');
//Missing piece of code where I split the URI for the JSON function that will POST the data object to be be calculated by the DROOLS/Rules Engine and the results passed back to the users web browser
...
}
}
}
Now in my separate function class I'd have something like this (Function.cs)
using System;
using newton.json;
using System.Net.Http;
namespace FunctionLibrary
{
public static class Functions
{
public static double DoMathA(string url, string arg1, string arg2)
{
double d1;
double d2;
if (!double.TryParse(arg1, out d1) || !double.TryParse(arg2, out d2))
{
throw new ArgumentException("Arguments to function 'DoMathA' must be numeric.");
}
//Data Object Format "{'myData':{'Id':'5','var1':'10','var2':'90'}}"
myCalcObject = "{'myData':{'Id':'5', & arg1 & :'10', & arg2 & :'90'}}"
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/json; charset=utf-8";
DataContractJsonSerializer ser = new DataContractJsonSerializer(data.GetType());
MemoryStream ms = new MemoryStream();
ser.WriteObject (myCalcObject)
String json = Encoding.UTF8.GetString(ms.ToArray());
StreamWriter writer = new StreamWriter(request.GetRequestStream());
writer.Write(json);
writer.Close();
}
}
...
//Missing piece of code where I want to return the results of the JSON PUT and GET to the calling webservice
//JSON output string looks like this {"YourResults":{"condition":"YourHairIsOnFire","alertlevel":100,"id":0}}
//return or parse (json) to XLMS on the users browser
}
I need help filling in the blanks so that the request URI is parsed properly to be passed into the JSON function and the reply JSON string be translated back as an xlms on the users browser. Any thoughts?
Update: I tried getting just the JSON section to work as a standalone C# class but I get that "Expected class..." error when I compile it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Net.Http;
using System.Net;
using Newtonsoft.Json;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(http://172.16.20.26:8080/myDrools/result);
request.Method = "POST";
request.ContentType = "application/json; charset=utf-8";
DataContractJsonSerializer ser = new DataContractJsonSerializer(data.GetType());
MemoryStream ms = new MemoryStream();
ser.WriteObject ("{'myData':{'Id':'5','var1':'4.5','var2':'8.7'}}")
String json = Encoding.UTF8.GetString(ms.ToArray());
StreamWriter writer = new StreamWriter(request.GetRequestStream());
writer.Write(json);
writer.Close();
What Am I doing wrong here? How can I get an XMLS output from this?
You have several bits and pieces. If you glue them together it can work.
Service Interface
Make sure your interface is capable of returning an object that holds the data you expect to return. By making fields nullable you can have variations in how the result looks like.
Notice the ResponseFormat=WebMessageFormat.Json to have json as the returned content type.
[ServiceContract]
public interface IExecFunction
{
[WebGet(UriTemplate = "/{function}/{args}", ResponseFormat=WebMessageFormat.Json)]
[OperationContract]
Result CalcThis(string function, string args);
}
// the result class that actsd as an container
public class Result
{
public Double DoubleResult { get; set; }
public Int32 IntResult { get; set; }
public string Message { get; set; }
}
Service Implementation
The service implementation is bit more involved because it first needs to parse the arguments and convert them to proper types. When that is done it can use reflection to find an appropriate method to call. The returned type is then converted/projected on the Result.
public class ExecFunctionService : IExecFunction
{
// this is a GET /fubar/1,2,3,4
public Result CalcThis(string function, string args)
{
// function=fubar
// args = 1,2,3,4
var allargs = args.Split(',');
// store each argument with their type
var typeList = new List<Tuple<Type, object>>();
// parsr to gind the best match
foreach(var arg in allargs)
{
// convert each argument string
// to a type that is supported
int i;
if (Int32.TryParse(arg, out i))
{
typeList.Add(new Tuple<Type,object>(typeof(Int32), i));
continue;
}
double d;
if (Double.TryParse(arg,
NumberStyles.AllowDecimalPoint,
new CultureInfo("en-us"),
out d))
{
typeList.Add(new Tuple<Type,object>(typeof(Double), d));
continue;
}
// if all fails assume string
typeList.Add(new Tuple<Type,object>(typeof(string), arg));
}
// find and call the correct method
// notice that parameters and their type do matter
// overloads of the same methodname with
// different types is supported
// Functions is the static type with methods to call
var method = typeof(Functions).GetMethod(
function,
BindingFlags.Static| BindingFlags.Public |BindingFlags.InvokeMethod,
null,
typeList.Select(ty => ty.Item1).ToArray(), //all types
null);
var callresult = method.Invoke(
null,
typeList.Select(ty => ty.Item2).ToArray()); // all values
// shape the output in the form you need
var result = new Result();
if(callresult is double)
{
result.DoubleResult = (double) callresult;
}
if (callresult is int)
{
result.IntResult = (int)callresult;
}
if (callresult is string)
{
result.Message = (string)callresult;
}
return result;
}
}
Your Functions to be called
This is the class that holds all your methods that can be called from the service.
// your calc functions go here
public static class Functions
{
public static double DoMathA(double arg1, double arg2)
{
return arg1 / arg2;
}
public static double DoMathB(int number, double factor)
{
return number * factor;
}
public static int DoMathC(string somestring)
{
return somestring.GetHashCode();
}
}
What does it look like?
Calling http://localhost/service1.svc/DoMathC/fubar will return:
{"DoubleResult":0,"IntResult":418978654,"Message":null}
and http://localhost/service1.svc/DoMathA/2.5,3.4 will return:
{"DoubleResult":0.73529411764705888,"IntResult":0,"Message":null}
and http://localhost/service1.svc/DoMathB/4,3.5 will return:
{"DoubleResult":14,"IntResult":0,"Message":null}
Think of it this way: instead of trying to glue together a string, return an object. The service will take care of stringifying it for you.
So, create a class with properties to match the JSON object, and then set your values into those properties, then return the object. Done.
Don't overthink it
Related
I'm having trouble with converting solidity's uint256 to readable c# object.
public Transaction DecodeInputData(Transaction tx)
{
EthApiContractService ethApi = new EthApiContractService(null);
var contract = ethApi.GetContract(Abi.Replace(#"\", string.Empty), Address);
var transfer = contract.GetFunction("transfer");
var decodedTx = transfer.DecodeInput(tx.input);
tx.to = (string)decodedTx[0].Result;
tx.value = "0x" + ((BigInteger)decodedTx[1].Result).ToString("x");
return tx;
}
Example Tx: https://etherscan.io/tx/0x622760ad1a0ead8d16641d5888b8c36cb67be5369556f8887499f4ad3e3d1c3d
We must able to convert decodedTx[1].Result variable ( its: {53809663494440740791636285293469688360281260987263635605451211260198698423701}) to 83218945020000000000.
We converting this value to hex to compatibility. But the hex i get is; "0x76f730b400000000000000000000000000000000000000000000000482e51595"
I am using Nethereum library with .net core 2.1
You are trying to decode the smart contract function parameters of a transaction. The smart contract is an ERC20 smart contract, and the function is the Transfer method.
To do so you need to do the following.
Create your Function message in this is scenario the Transfer Function.
Retrieve the transaction
Using the FunctionMessage extension method DecodeTransaction, decode the values of the original Transaction input.
using Nethereum.Web3;
using Nethereum.ABI.FunctionEncoding.Attributes;
using Nethereum.Contracts.CQS;
using Nethereum.Util;
using Nethereum.Web3.Accounts;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Contracts;
using Nethereum.Contracts.Extensions;
using System;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
public class GetStartedSmartContracts
{
[Function("transfer", "bool")]
public class TransferFunction : FunctionMessage
{
[Parameter("address", "_to", 1)]
public string To { get; set; }
[Parameter("uint256", "_value", 2)]
public BigInteger TokenAmount { get; set; }
}
public static async Task Main()
{
var url = "https://mainnet.infura.io";
var web3 = new Web3(url);
var txn = await web3.Eth.Transactions.GetTransactionByHash.SendRequestAsync("0x622760ad1a0ead8d16641d5888b8c36cb67be5369556f8887499f4ad3e3d1c3d");
var transfer = new TransferFunction().DecodeTransaction(txn);
Console.WriteLine(transfer.TokenAmount);
//BAT has 18 decimal places the same as Wei
Console.WriteLine(Web3.Convert.FromWei(transfer.TokenAmount));
}
}
You can test this in the http://playground.nethereum.com
Or you can also do, if want to check what function is:
var functionCallDecoder = new FunctionCallDecoder();
if(functionCallDecoder.IsDataForFunction(ABITypedRegistry.GetFunctionABI<TransferFunction>().Sha3Signature, txn.Input)) {
var transfer = new TransferFunction().DecodeInput(txn.Input);
Console.WriteLine(Web3.Convert.FromWei(transfer.TokenAmount));
Console.WriteLine(transfer.To);
}
I have created the following classes. one base class IObject and 2 derived classes A and B.
[KnownType(typeof(B))]
[KnownType(typeof(A))]
[DataContract(Name = "IObject")]
public class IObject
{
}
[DataContract(Name="A")]
public class A : IObject
{
[DataMember]
public string s1 { get; set; } // Tag Name as it will be presented to the user
}
[DataContract(Name="B")]
public class B : IObject
{
[DataMember]
public string s2 { get; set; }
}
I have also created the following Service:
[ServiceKnownType(typeof(B))]
[ServiceKnownType(typeof(A))]
public void GetR(IObject obj)
{
B other = (B)obj;
}
What i want to do is get an instance of A or B but i don't know which one i will get so i expect to get an IObject and cast it later to either A or B as shown in the example i put.
What happens when i send a json string containing s2 string is that i get IObject instance and not B instance.
What is wrong with the code?
Example of the client i'm using :
var url = serviceURL;
var data = {s2: "Data"};
var json = JSON.stringify(data);
$.ajax({
type: "POST",
url: url,
data: data,
contentType: "application/json",
dataType: 'json'
});
Edited: I have uploaded an example code to gitHub at the following link:
https://github.com/AlgoEitan/27588144
The client there is a c# client (I've tried it with both c# and javascript - web browser client)
DataContractJsonSerializer has a specific format in which it stores hints about known type information for polymorphic types, which one can discover with a bit of testing. I copied and pasted your classes as-is, and created the following test code:
public static class DataContractJsonSerializerPolymorphismTest
{
public static void Test()
{
var a1 = new A() { s1 = "A" };
var b1 = new B() { s2 = "B" };
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(IObject));
var jsona1 = DataContractJsonSerializerHelper.GetJson(a1, serializer);
var jsonb1 = DataContractJsonSerializerHelper.GetJson(b1, serializer);
Debug.WriteLine(jsona1);
Debug.WriteLine(jsonb1);
var newa1 = DataContractJsonSerializerHelper.GetObject<IObject>(jsona1, serializer);
Debug.Assert(newa1.GetType() == a1.GetType()); // No assert
}
}
With that, the following JSON was created:
{"__type":"A:#Tile.DataContractJsonSerializerPolymorphism","s1":"A"}
{"__type":"B:#Tile.DataContractJsonSerializerPolymorphism","s2":"B"}
Where Tile.DataContractJsonSerializerPolymorphism happens to be the name of the CLR namespace in my test application. So, as you can see, the known type information got tucked away in this extra JSON property __type. Now, if you were using DataContractJsonSerializerHelper in your client also, you would never know this, because communication would just work. But you are using JSON.stringify() which does not have this logic. So, you may have to manually add the "__type":"DataContractName:DataContractNamespace" property on the client side.
More about the format for polymorphic types can be found here. (I only tracked this documentation down after finding the hidden __type parameter, which gave me an extra Google search term.)
FYI, here is the helper class I used with the test code:
public static class DataContractJsonSerializerHelper
{
private static MemoryStream GenerateStreamFromString(string value)
{
return new MemoryStream(Encoding.Unicode.GetBytes(value ?? ""));
}
public static string GetJson<T>(T obj, DataContractJsonSerializer serializer) where T : class
{
using (var memory = new MemoryStream())
{
serializer.WriteObject(memory, obj);
memory.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(memory))
{
return reader.ReadToEnd();
}
}
}
public static string GetJson<T>(T obj) where T : class
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
return GetJson(obj, serializer);
}
public static T GetObject<T>(string json) where T : class
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
return GetObject<T>(json, serializer);
}
public static T GetObject<T>(string json, DataContractJsonSerializer serializer) where T : class
{
T obj = null;
using (var stream = GenerateStreamFromString(json))
{
obj = (T)serializer.ReadObject(stream);
}
return obj;
}
}
after long hours of searching for a good JSON library, i found Newtownsoft.json, so i started using it to decode a json text i get from a web request, i don't know if the json is being decoded properly
the class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//Request library
using System.Net;
using System.IO;
using Newtonsoft.Json;
namespace TestApplication
{
class Connect
{
public string id;
public string type;
private string api = "https://api.stackexchange.com/2.2/";
private string options = "?order=desc&sort=name&site=stackoverflow";
public object request()
{
string totalUrl = this.join(id);
string json = this.HttpGet(totalUrl);
return this.decodeJson(json);
}
private string join(string s)
{
return api + type + "/" + s + options;
}
private string HttpGet(string URI)
{
string html = string.Empty;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URI);
request.AutomaticDecompression = DecompressionMethods.GZip;
request.ContentType = "application/json; charset=utf-8";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (Stream stream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(stream))
{
html = reader.ReadToEnd();
}
return html;
}
private object decodeJson(string json)
{
object js = JsonConvert.DeserializeObject(json);
return js;
}
}
}
the class object is being accessed from the form in this way:
Connect rq = new
rq.id = usernameText.Text;
rq.type = "users";
Debug.WriteLine(rq.request());
i don't know why i can't do rq.request().items or rq.request()["items"], i am still learning c# and i would like to know how to access the json object members in the proper way.
NOTE: this is the first desktop program i'm developing, i am a php/nodejs developer and i wanted to make an application that will connect to stack exchange database and retrieve user's info.
The return type of your request method is object, and so the returned instance will not have a property named items.
You'll need to use generic methods and specify the correct type parameter.
Try changing your decodeJson method to this:
private T decodeJson<T>(string json)
{
var js = JsonConvert.DeserializeObject<T>(json);
return js;
}
And then change your request method to this:
public T request<T>()
{
string totalUrl = this.join(id);
string json = HttpGet(totalUrl);
return decodeJson<T>(json);
}
Now write a class with properties that match the name and type of the properties in the JSON returned from the web request.
Then specify the type of this new class as the type parameter for your call to the request method.
For example, if you expected the JSON to contain a string called 'Name' and an int called 'Age', write a class that is something like this:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
and then call request like this
Person myPerson = rq.request<Person>();
and you'll be left with an instance of Person with Name and Age properties
How does one read a very large JSON file into an array in c# to be split up for later processing?
I have managed to get something working that will:
Read the file Miss out headers and only read values into array.
Place a certain amount of values on each line of an array. (So I
could later split it an put into 2d array)
This was done with the code below but it crashes the program after entering a few lines into the array. This might have to do with the file size.
// If the file extension was a jave file the following
// load method will be use else it will move on to the
// next else if statement
if (fileExtension == ".json")
{
int count = 0;
int count2 = 0;
int inOrOut = 0;
int nRecords=1;
JsonTextReader reader = new JsonTextReader(new StreamReader(txtLoaction.Text));
string[] rawData = new string[5];
while (reader.Read())
{
if (reader.Value != null)
if (inOrOut == 1)
{
if (count == 6)
{
nRecords++;
Array.Resize(ref rawData, nRecords);
//textBox1.Text += "\r\n";
count = 0;
}
rawData[count2] += reader.Value + ","; //+"\r\n"
inOrOut = 0;
count++;
if (count2 == 500)
{
MessageBox.Show(rawData[499]);
}
}
else
{
inOrOut = 1;
}
}
}
A snippet of the JSON I am working with is:
[
{ "millis": "1000",
"stamp": "1273010254",
"datetime": "2010/5/4 21:57:34",
"light": "333",
"temp": "78.32",
"vcc": "3.54" },
]
I need the values out of this JSON. For example, I need "3.54", but I would not want it to print the "vcc".
How can one read a JSON file in and only extract the data needed to be put into an array?
How about making everything easier with Json.NET?
public void LoadJson()
{
using (StreamReader r = new StreamReader("file.json"))
{
string json = r.ReadToEnd();
List<Item> items = JsonConvert.DeserializeObject<List<Item>>(json);
}
}
public class Item
{
public int millis;
public string stamp;
public DateTime datetime;
public string light;
public float temp;
public float vcc;
}
You can even get the values dynamically without declaring Item class.
dynamic array = JsonConvert.DeserializeObject(json);
foreach(var item in array)
{
Console.WriteLine("{0} {1}", item.temp, item.vcc);
}
Doing this yourself is an awful idea. Use Json.NET. It has already solved the problem better than most programmers could if they were given months on end to work on it. As for your specific needs, parsing into arrays and such, check the documentation, particularly on JsonTextReader. Basically, Json.NET handles JSON arrays natively and will parse them into strings, ints, or whatever the type happens to be without prompting from you. Here is a direct link to the basic code usages for both the reader and the writer, so you can have that open in a spare window while you're learning to work with this.
This is for the best: Be lazy this time and use a library so you solve this common problem forever.
Answer for .NET Core
You can just use the built-in System.Text.Json instead of the 3rd-party Json.NET. To promote reuse, the JSON-file-reading functionality belongs in its own class and should be generic rather than hard-coded to a certain type (Item). Here's a full example:
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
namespace Project
{
class Program
{
static async Task Main()
{
Item item = await JsonFileReader.ReadAsync<Item>(#"C:\myFile.json");
}
}
public static class JsonFileReader
{
public static async Task<T> ReadAsync<T>(string filePath)
{
using FileStream stream = File.OpenRead(filePath);
return await JsonSerializer.DeserializeAsync<T>(stream);
}
}
public class Item
{
public int millis;
public string stamp;
public DateTime datetime;
public string light;
public float temp;
public float vcc;
}
}
Or, if you prefer something simpler/synchronous:
class Program
{
static void Main()
{
Item item = JsonFileReader.Read<Item>(#"C:\myFile.json");
}
}
public static class JsonFileReader
{
public static T Read<T>(string filePath)
{
string text = File.ReadAllText(filePath);
return JsonSerializer.Deserialize<T>(text);
}
}
This can also be done in the following way:
JObject data = JObject.Parse(File.ReadAllText(MyFilePath));
string jsonFilePath = #"C:\MyFolder\myFile.json";
string json = File.ReadAllText(jsonFilePath);
Dictionary<string, object> json_Dictionary = (new JavaScriptSerializer()).Deserialize<Dictionary<string, object>>(json);
foreach (var item in json_Dictionary)
{
// parse here
}
Based on #L.B.'s solution, the (typed as Object rather than Anonymous) VB code is
Dim oJson As Object = JsonConvert.DeserializeObject(File.ReadAllText(MyFilePath))
I should mention that this is quick and useful for constructing HTTP call content where the type isn't required. And using Object rather than Anonymous means you can maintain Option Strict On in your Visual Studio environment - I hate turning that off.
For any of the JSON parse, use the website http://json2csharp.com/ (easiest way) to convert your JSON into C# class to deserialize your JSON into C# object.
public class JSONClass
{
public string name { get; set; }
public string url { get; set; }
public bool visibility { get; set; }
public string idField { get; set; }
public bool defaultEvents { get; set; }
public string type { get; set; }
}
Then use the JavaScriptSerializer (from System.Web.Script.Serialization), in case you don't want any third party DLL like newtonsoft.
using (StreamReader r = new StreamReader("jsonfile.json"))
{
string json = r.ReadToEnd();
JavaScriptSerializer jss = new JavaScriptSerializer();
var Items = jss.Deserialize<JSONClass>(json);
}
Then you can get your object with Items.name or Items.Url etc.
Very Easiest way I found on online to work with .JSON file in C#(or any other Programming Language)
Prerequisite:-
Install Newtonsoft.Json Library into your Project
Newtonsoft.Json
and here is the URL -> https://app.quicktype.io/
Steps
1> go to this URL - https://app.quicktype.io/
2> Copy and Paste your JSON file structure into Left sidebar
app.quicktype.io
3> Select required Language (here C#) from Options menu
4> Copy generated code and go to your Project and Create a new .cs file with the same name(here "Welcome.cs")
Welcome.cs
5> Paste all generated code into the newly created class.
Welcome.cs pasted Code
6> that's it. :)
Steps to Access value
1> Go to Main Program .cs file or wherever you need to access it.
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Access Json values using Keys.>");
String jsonString = new StreamReader("give <.json> file Path here").ReadToEnd();
// use below syntax to access JSON file
var jsonFile = Welcome.FromJson(jsonString);
string FileName = jsonFile.File;
long Lvl = jsonFile.Level;
bool isTrue = jsonFile.CSharp;
Console.WriteLine(FileName);//JSON
Console.WriteLine(Lvl);//1
Console.WriteLine(isTrue);//true
}
}
For finding the right path I'm using
var pathToJson = Path.Combine("my","path","config","default.Business.Area.json");
var r = new StreamReader(pathToJson);
var myJson = r.ReadToEnd();
// my/path/config/default.Business.Area.json
[...] do parsing here
Path.Combine uses the Path.PathSeparator and it checks whether the first path has already a separator at the end so it will not duplicate the separators. Additionally, it checks whether the path elements to combine have invalid chars.
See https://stackoverflow.com/a/32071002/4420355
There is a faster way of parsing json then Json.Net . If you are using .net core 3.0 or up then you can use the System.Text.Json nuget package to serialize or deserialize.
you need to add:
using System.Text.Json
And then you can serialize as:
var jsonStr = JsonSerializer.Serialize(model);
And Deserialize as:
var model = JsonSerializer.Deserialize(jsonStr);
This code can help you:
string _filePath = Path.GetDirectoryName(System.AppDomain.CurrentDomain.BaseDirectory);
JObject data = JObject.Parse(_filePath );
There is an easier way to get JSON from file or from the Web:
Json.Net.Curl
Install-Package Json.Net.Curl
// get JObject from local file system
var json = Json.Net.Curl.Get(#"data\JObjectUnitTest1.json");
var json = await Json.Net.Curl.GetAsync(#"data\JObjectUnitTest1.json")
// get JObject from Server
var json = await Json.Net.Curl.GetAsync("http://myserver.com/data.json");
GitHub Project
Nuget
With Cinchoo ETL, an open source library, parsing of very large JSON file is iterative and simple to use
1. Dynamic Method: - No POCO class required
string json = #"
[
{
""millis"": ""1000"",
""stamp"": ""1273010254"",
""datetime"": ""2010/5/4 21:57:34"",
""light"": ""333"",
""temp"": ""78.32"",
""vcc"": ""3.54""
},
{
""millis"": ""2000"",
""stamp"": ""1273010254"",
""datetime"": ""2010/5/4 21:57:34"",
""light"": ""333"",
""temp"": ""78.32"",
""vcc"": ""3.54""
}
]
";
using (var r = ChoJSONReader.LoadText(json))
{
foreach (var rec in r)
Console.WriteLine(rec.Dump());
}
Sample fiddle: https://dotnetfiddle.net/mo1qvw
2. POCO Method:
Define POCO class matching json attributes
public class Item
{
public int Millis { get; set; }
public string Stamp { get; set; }
public DateTime Datetime { get; set; }
public string Light { get; set; }
public float Temp { get; set; }
public float Vcc { get; set; }
}
Then using the parser to load the JSON as below
string json = #"
[
{
""millis"": ""1000"",
""stamp"": ""1273010254"",
""datetime"": ""2010/5/4 21:57:34"",
""light"": ""333"",
""temp"": ""78.32"",
""vcc"": ""3.54""
},
{
""millis"": ""2000"",
""stamp"": ""1273010254"",
""datetime"": ""2010/5/4 21:57:34"",
""light"": ""333"",
""temp"": ""78.32"",
""vcc"": ""3.54""
}
]
";
using (var r = ChoJSONReader<Item>.LoadText(json))
{
foreach (var rec in r)
Console.WriteLine(ChoUtility.Dump(rec));
}
Sample fiddle: https://dotnetfiddle.net/fRWu0w
Disclaimer: I'm author of this library.
I'm implementing a client-server application, and am looking into various ways to serialize and transmit data. I began working with Xml Serializers, which worked rather well, but generate data slowly, and make large objects, especially when they need to be sent over the net. So I started looking into Protobuf, and protobuf-net.
My problem lies in the fact that protobuf doesn't sent type information with it. With Xml Serializers, I was able to build a wrapper which would send and receive any various (serializable) object over the same stream, since object serialized into Xml contain the type name of the object.
ObjectSocket socket = new ObjectSocket();
socket.AddTypeHandler(typeof(string)); // Tells the socket the types
socket.AddTypeHandler(typeof(int)); // of objects we will want
socket.AddTypeHandler(typeof(bool)); // to send and receive.
socket.AddTypeHandler(typeof(Person)); // When it gets data, it looks for
socket.AddTypeHandler(typeof(Address)); // these types in the Xml, then uses
// the appropriate serializer.
socket.Connect(_host, _port);
socket.Send(new Person() { ... });
socket.Send(new Address() { ... });
...
Object o = socket.Read();
Type oType = o.GetType();
if (oType == typeof(Person))
HandlePerson(o as Person);
else if (oType == typeof(Address))
HandleAddress(o as Address);
...
I've considered a few solutions to this, including creating a master "state" type class, which is the only type of object sent over my socket. This moves away from the functionality I've worked out with Xml Serializers, though, so I'd like to avoid that direction.
The second option would be to wrap protobuf objects in some type of wrapper, which defines the type of object. (This wrapper would also include information such as packet ID, and destination.) It seems silly to use protobuf-net to serialize an object, then stick that stream between Xml tags, but I've considered it. Is there an easy way to get this functionality out of protobuf or protobuf-net?
I've come up with a third solution, and posted it below, but if you have a better one, please post it too!
Information on field bounds bug (using System.String):
Hashing:
protected static int ComputeTypeField(Type type) // System.String
{
byte[] data = ASCIIEncoding.ASCII.GetBytes(type.FullName);
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
return Math.Abs(BitConverter.ToInt32(md5.ComputeHash(data), 0));
}
Serialization:
using (MemoryStream stream = new MemoryStream())
{
Serializer.NonGeneric.SerializeWithLengthPrefix
(stream, o, PrefixStyle.Base128, field); // field = 600542181
byte[] data = stream.ToArray();
_pipe.Write(data, 0, data.Length);
}
Deserializaion:
using (MemoryStream stream = new MemoryStream(_buffer.Peek()))
{
lock (_mapLock)
{
success = Serializer.NonGeneric.TryDeserializeWithLengthPrefix
(stream, PrefixStyle.Base128, field => _mappings[field], out o);
}
if (success)
_buffer.Clear((int)stream.Position);
else
{
int len;
if (Serializer.TryReadLengthPrefix(stream, PrefixStyle.Base128, out len))
_buffer.Clear(len);
}
}
field => _mappings[field] throws a KeyNotFoundException while looking for 63671269.
If I replace ToInt32 with ToInt16 in the hash function, the field value is set to 29723 and it works. It also works if I explicitly define System.String's field to 1. Explicitly defining the field to 600542181 has the same effect as using the hash function to define it. The value of the string being serialized does not change the outcome.
This functionality is actually built in, albeit not obviously.
In this scenario, it is anticipated that you would designate a unique number per message type. The overload you are using passes them all in as "field 1", but there is an overload that lets you include this extra header information (it is still the job of the calling code to decide how to map numbers to types, though). You can then specify different types as different fields is the stream (note: this only works with the base-128 prefix style).
I'll need to double check, but the intention is that something like the following should work:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ProtoBuf;
static class Program
{
static void Main()
{
using (MemoryStream ms = new MemoryStream())
{
WriteNext(ms, 123);
WriteNext(ms, new Person { Name = "Fred" });
WriteNext(ms, "abc");
ms.Position = 0;
while (ReadNext(ms)) { }
}
}
// *** you need some mechanism to map types to fields
static readonly IDictionary<int, Type> typeLookup = new Dictionary<int, Type>
{
{1, typeof(int)}, {2, typeof(Person)}, {3, typeof(string)}
};
static void WriteNext(Stream stream, object obj) {
Type type = obj.GetType();
int field = typeLookup.Single(pair => pair.Value == type).Key;
Serializer.NonGeneric.SerializeWithLengthPrefix(stream, obj, PrefixStyle.Base128, field);
}
static bool ReadNext(Stream stream)
{
object obj;
if (Serializer.NonGeneric.TryDeserializeWithLengthPrefix(stream, PrefixStyle.Base128, field => typeLookup[field], out obj))
{
Console.WriteLine(obj);
return true;
}
return false;
}
}
[ProtoContract] class Person {
[ProtoMember(1)]public string Name { get; set; }
public override string ToString() { return "Person: " + Name; }
}
Note that this doesn't currently work in the v2 build (since the "WithLengthPrefix" code is incomplete), but I'll go and test it on v1. If it works, I'll all the above scenario to the test suite to ensure it does work in v2.
Edit:
yes, it does work fine on "v1", with output:
123
Person: Fred
abc
I've come up with another solution, but I decided to put it as an answer, instead of in the question, because that makes more sense to me. It's pretty ugly, in my opinion, and I've been warned against using reflection, so please comment on it or provide better answers if you have them. Thanks!
class Program
{
static void Main(string[] args)
{
Person person = new Person
{
Id = 12345,
Name = "Fred",
Address = new Address
{
Line1 = "Flat 1",
Line2 = "The Meadows"
}
};
object value;
using (Stream stream = new MemoryStream())
{
Send<Person>(stream, person);
stream.Position = 0;
value = Read(stream);
person = value as Person;
}
}
static void Send<T>(Stream stream, T value)
{
Header header = new Header()
{
Guid = Guid.NewGuid(),
Type = typeof(T)
};
Serializer.SerializeWithLengthPrefix<Header>(stream, header, PrefixStyle.Base128);
Serializer.SerializeWithLengthPrefix<T>(stream, value, PrefixStyle.Base128);
}
static object Read(Stream stream)
{
Header header;
header = Serializer.DeserializeWithLengthPrefix<Header>
(stream, PrefixStyle.Base128);
MethodInfo m = typeof(Serializer).GetMethod("DeserializeWithLengthPrefix",
new Type[] {typeof(Stream), typeof(PrefixStyle)}).MakeGenericMethod(header.Type);
Object value = m.Invoke(null, new object[] {stream, PrefixStyle.Base128} );
return value;
}
}
[ProtoContract]
class Header
{
public Header() { }
[ProtoMember(1, IsRequired = true)]
public Guid Guid { get; set; }
[ProtoIgnore]
public Type Type { get; set; }
[ProtoMember(2, IsRequired = true)]
public string TypeName
{
get { return this.Type.FullName; }
set { this.Type = Type.GetType(value); }
}
}
[ProtoContract]
class Person {
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
[ProtoMember(3)]
public Address Address { get; set; }
}
[ProtoContract]
class Address {
[ProtoMember(1)]
public string Line1 { get; set; }
[ProtoMember(2)]
public string Line2 { get; set; }
}