Parsing JSON Object with Variable Properties into Strongly Typed Object

Parsing JSON Object with variable properties into strongly typed object

If you must have strongly typed result I would deserialize Profile as a dictionary of superposition of properties

class AbscdeClass
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
public string D { get; set; }
public string E { get; set; }
}

class JsonBody
{
public Dictionary<string, AbscdeClass> Profile { get; set; }
}

and parse original JSON text as

JsonBody json = JsonConvert.DeserializeObject<JsonBody>(jsonString);

How to convert JSON to Objects with changing Keys

You can use below class structure to deserialize your json.

class Rooot
{
public Dictionary<string, BankDetail> Result { get; set; }
}

public class BankDetail
{
public string BANK { get; set; }
public string IFSC { get; set; }
public string BRANCH { get; set; }
public string ADDRESS { get; set; }
public object CONTACT { get; set; }
public string CITY { get; set; }
public string DISTRICT { get; set; }
public string STATE { get; set; }
}

Usage:

string json = File.ReadAllText(IFSCCodeFile);

Rooot rooot = JsonConvert.DeserializeObject<Rooot>(json);

foreach (var item in rooot.Result)
{
Console.WriteLine(item.Key);
Console.WriteLine(item.Value.BANK ?? "NULL");
Console.WriteLine(item.Value.IFSC ?? "NULL");
Console.WriteLine(item.Value.BRANCH ?? "NULL");
Console.WriteLine(item.Value.ADDRESS ?? "NULL");
Console.WriteLine(item.Value.CONTACT ?? "NULL");
Console.WriteLine(item.Value.CITY ?? "NULL");
Console.WriteLine(item.Value.DISTRICT ?? "NULL");
Console.WriteLine(item.Value.STATE ?? "NULL");
Console.WriteLine();
}

Console.ReadLine();

Output:

Sample Image

Parsing JSON into C# Object - Get Properties Dynamically

You have several issues here:

  • Your root object has a large, variable number of properties whose values correspond to a fixed data type PoloniexPairInfo. Since you don't want to create a root type that hardcodes all these properties, you can deserialize to a Dictionary<string, PoloniexPairInfo> as shown in Create a strongly typed c# object from json object with ID as the name.

  • The Bid and Ask properties are represented in JSON as an array of arrays of values of different types:

    [
    [
    "0.00007359",
    163.59313969
    ]
    ]

    You would like to map the inner arrays to a fixed POCO PoloniexPriceVolume by binding values at specific array indices to specific c# properties. You can do this using ObjectToArrayConverter<PoloniexPriceVolume> from C# JSON.NET - Deserialize response that uses an unusual data structure.

  • Finally, the JSON value "isFrozen" has a string value "0" but you would like to map it to a bool value public bool IsFrozen { get; set; }. You can do this by adapting BoolConverter from Convert an int to bool with Json.Net.

Putting all this together, your can deserialize your JSON with the following models and converters:

[JsonConverter(typeof(ObjectToArrayConverter<PoloniexPriceVolume>))]
public class PoloniexPriceVolume
{
[JsonProperty(Order = 1)]
public string Price { get; set; }
[JsonProperty(Order = 2)]
public double Volume { get; set; }
}

public class PoloniexPairInfo
{
public List<PoloniexPriceVolume> Asks { get; set; }
public List<PoloniexPriceVolume> Bids { get; set; }
[JsonConverter(typeof(BoolConverter))]
public bool IsFrozen { get; set; }
public int Seq { get; set; }
}

public class ObjectToArrayConverter<T> : JsonConverter
{
//https://stackoverflow.com/a/39462464/3744182
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var objectType = value.GetType();
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
if (contract == null)
throw new JsonSerializationException(string.Format("invalid type {0}.", objectType.FullName));
writer.WriteStartArray();
foreach (var property in SerializableProperties(contract))
{
var propertyValue = property.ValueProvider.GetValue(value);
if (property.Converter != null && property.Converter.CanWrite)
property.Converter.WriteJson(writer, propertyValue, serializer);
else
serializer.Serialize(writer, propertyValue);
}
writer.WriteEndArray();
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
if (contract == null)
throw new JsonSerializationException(string.Format("invalid type {0}.", objectType.FullName));

if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartArray)
throw new JsonSerializationException(string.Format("token {0} was not JsonToken.StartArray", reader.TokenType));

// Not implemented: JsonObjectContract.CreatorParameters, serialization callbacks,
existingValue = existingValue ?? contract.DefaultCreator();

using (var enumerator = SerializableProperties(contract).GetEnumerator())
{
while (true)
{
switch (reader.ReadToContentAndAssert().TokenType)
{
case JsonToken.EndArray:
return existingValue;

default:
if (!enumerator.MoveNext())
{
reader.Skip();
break;
}
var property = enumerator.Current;
object propertyValue;
// TODO:
// https://www.newtonsoft.com/json/help/html/Properties_T_Newtonsoft_Json_Serialization_JsonProperty.htm
// JsonProperty.ItemConverter, ItemIsReference, ItemReferenceLoopHandling, ItemTypeNameHandling, DefaultValue, DefaultValueHandling, ReferenceLoopHandling, Required, TypeNameHandling, ...
if (property.Converter != null && property.Converter.CanRead)
propertyValue = property.Converter.ReadJson(reader, property.PropertyType, property.ValueProvider.GetValue(existingValue), serializer);
else
propertyValue = serializer.Deserialize(reader, property.PropertyType);
property.ValueProvider.SetValue(existingValue, propertyValue);
break;
}
}
}
}

static IEnumerable<JsonProperty> SerializableProperties(JsonObjectContract contract)
{
return contract.Properties.Where(p => !p.Ignored && p.Readable && p.Writable);
}
}

public static partial class JsonExtensions
{
//https://stackoverflow.com/a/39462464/3744182
public static JsonReader ReadToContentAndAssert(this JsonReader reader)
{
return reader.ReadAndAssert().MoveToContentAndAssert();
}

public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}

public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}

public class BoolConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((bool)value) ? "1" : "0");
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;

var token = JToken.Load(reader);

if (token.Type == JTokenType.Boolean)
return (bool)token;
return token.ToString() != "0";
}

public override bool CanConvert(Type objectType)
{
return objectType == typeof(bool);
}
}

And finally, do:

var orderBook = JsonConvert.DeserializeObject<Dictionary<string, PoloniexPairInfo>>(jsonString);

Working .Net fiddle.

How to parse a JSON object to a TypeScript Object

The reason that the compiler lets you cast the object returned from JSON.parse to a class is because typescript is based on structural subtyping.

You don't really have an instance of an Employee, you have an object (as you see in the console) which has the same properties.

A simpler example:

class A {
constructor(public str: string, public num: number) {}
}

function logA(a: A) {
console.log(`A instance with str: "${ a.str }" and num: ${ a.num }`);
}

let a1 = { str: "string", num: 0, boo: true };
let a2 = new A("stirng", 0);
logA(a1); // no errors
logA(a2);

(code in playground)

There's no error because a1 satisfies type A because it has all of its properties, and the logA function can be called with no runtime errors even if what it receives isn't an instance of A as long as it has the same properties.

That works great when your classes are simple data objects and have no methods, but once you introduce methods then things tend to break:

class A {
constructor(public str: string, public num: number) { }

multiplyBy(x: number): number {
return this.num * x;
}
}

// this won't compile:
let a1 = { str: "string", num: 0, boo: true } as A; // Error: Type '{ str: string; num: number; boo: boolean; }' cannot be converted to type 'A'

// but this will:
let a2 = { str: "string", num: 0 } as A;

// and then you get a runtime error:
a2.multiplyBy(4); // Error: Uncaught TypeError: a2.multiplyBy is not a function

(code in playground)


Edit

This works just fine:

const employeeString = '{"department":"<anystring>","typeOfEmployee":"<anystring>","firstname":"<anystring>","lastname":"<anystring>","birthdate":"<anydate>","maxWorkHours":0,"username":"<anystring>","permissions":"<anystring>","lastUpdate":"<anydate>"}';
let employee1 = JSON.parse(employeeString);
console.log(employee1);

(code in playground)

If you're trying to use JSON.parse on your object when it's not a string:

let e = {
"department": "<anystring>",
"typeOfEmployee": "<anystring>",
"firstname": "<anystring>",
"lastname": "<anystring>",
"birthdate": "<anydate>",
"maxWorkHours": 3,
"username": "<anystring>",
"permissions": "<anystring>",
"lastUpdate": "<anydate>"
}
let employee2 = JSON.parse(e);

Then you'll get the error because it's not a string, it's an object, and if you already have it in this form then there's no need to use JSON.parse.

But, as I wrote, if you're going with this way then you won't have an instance of the class, just an object that has the same properties as the class members.

If you want an instance then:

let e = new Employee();
Object.assign(e, {
"department": "<anystring>",
"typeOfEmployee": "<anystring>",
"firstname": "<anystring>",
"lastname": "<anystring>",
"birthdate": "<anydate>",
"maxWorkHours": 3,
"username": "<anystring>",
"permissions": "<anystring>",
"lastUpdate": "<anydate>"
});

Mapping JSON to C# classes when there are multiple keys

Your problem is that your class models don't match the json schema of the API output. The output isn't a list of stocks, it is a mapping of StockName: Stock

You can deserialize it as a Dictionary<string, Stock> rather than a Root and that should get you where you need to be.

How do I initialize a TypeScript Object with a JSON-Object?

These are some quick shots at this to show a few different ways. They are by no means "complete" and as a disclaimer, I don't think it's a good idea to do it like this. Also the code isn't too clean since I just typed it together rather quickly.

Also as a note: Of course deserializable classes need to have default constructors as is the case in all other languages where I'm aware of deserialization of any kind. Of course, Javascript won't complain if you call a non-default constructor with no arguments, but the class better be prepared for it then (plus, it wouldn't really be the "typescripty way").

Option #1: No run-time information at all

The problem with this approach is mostly that the name of any member must match its class. Which automatically limits you to one member of same type per class and breaks several rules of good practice. I strongly advise against this, but just list it here because it was the first "draft" when I wrote this answer (which is also why the names are "Foo" etc.).

module Environment {
export class Sub {
id: number;
}

export class Foo {
baz: number;
Sub: Sub;
}
}

function deserialize(json, environment, clazz) {
var instance = new clazz();
for(var prop in json) {
if(!json.hasOwnProperty(prop)) {
continue;
}

if(typeof json[prop] === 'object') {
instance[prop] = deserialize(json[prop], environment, environment[prop]);
} else {
instance[prop] = json[prop];
}
}

return instance;
}

var json = {
baz: 42,
Sub: {
id: 1337
}
};

var instance = deserialize(json, Environment, Environment.Foo);
console.log(instance);

Option #2: The name property

To get rid of the problem in option #1, we need to have some kind of information of what type a node in the JSON object is. The problem is that in Typescript, these things are compile-time constructs and we need them at runtime – but runtime objects simply have no awareness of their properties until they are set.

One way to do it is by making classes aware of their names. You need this property in the JSON as well, though. Actually, you only need it in the json:

module Environment {
export class Member {
private __name__ = "Member";
id: number;
}

export class ExampleClass {
private __name__ = "ExampleClass";

mainId: number;
firstMember: Member;
secondMember: Member;
}
}

function deserialize(json, environment) {
var instance = new environment[json.__name__]();
for(var prop in json) {
if(!json.hasOwnProperty(prop)) {
continue;
}

if(typeof json[prop] === 'object') {
instance[prop] = deserialize(json[prop], environment);
} else {
instance[prop] = json[prop];
}
}

return instance;
}

var json = {
__name__: "ExampleClass",
mainId: 42,
firstMember: {
__name__: "Member",
id: 1337
},
secondMember: {
__name__: "Member",
id: -1
}
};

var instance = deserialize(json, Environment);
console.log(instance);

Option #3: Explicitly stating member types

As stated above, the type information of class members is not available at runtime – that is unless we make it available. We only need to do this for non-primitive members and we are good to go:

interface Deserializable {
getTypes(): Object;
}

class Member implements Deserializable {
id: number;

getTypes() {
// since the only member, id, is primitive, we don't need to
// return anything here
return {};
}
}

class ExampleClass implements Deserializable {
mainId: number;
firstMember: Member;
secondMember: Member;

getTypes() {
return {
// this is the duplication so that we have
// run-time type information :/
firstMember: Member,
secondMember: Member
};
}
}

function deserialize(json, clazz) {
var instance = new clazz(),
types = instance.getTypes();

for(var prop in json) {
if(!json.hasOwnProperty(prop)) {
continue;
}

if(typeof json[prop] === 'object') {
instance[prop] = deserialize(json[prop], types[prop]);
} else {
instance[prop] = json[prop];
}
}

return instance;
}

var json = {
mainId: 42,
firstMember: {
id: 1337
},
secondMember: {
id: -1
}
};

var instance = deserialize(json, ExampleClass);
console.log(instance);

Option #4: The verbose, but neat way

Update 01/03/2016: As @GameAlchemist pointed out in the comments (idea, implementation), as of Typescript 1.7, the solution described below can be written in a better way using class/property decorators.

Serialization is always a problem and in my opinion, the best way is a way that just isn't the shortest. Out of all the options, this is what I'd prefer because the author of the class has full control over the state of deserialized objects. If I had to guess, I'd say that all other options, sooner or later, will get you in trouble (unless Javascript comes up with a native way for dealing with this).

Really, the following example doesn't do the flexibility justice. It really does just copy the class's structure. The difference you have to keep in mind here, though, is that the class has full control to use any kind of JSON it wants to control the state of the entire class (you could calculate things etc.).

interface Serializable<T> {
deserialize(input: Object): T;
}

class Member implements Serializable<Member> {
id: number;

deserialize(input) {
this.id = input.id;
return this;
}
}

class ExampleClass implements Serializable<ExampleClass> {
mainId: number;
firstMember: Member;
secondMember: Member;

deserialize(input) {
this.mainId = input.mainId;

this.firstMember = new Member().deserialize(input.firstMember);
this.secondMember = new Member().deserialize(input.secondMember);

return this;
}
}

var json = {
mainId: 42,
firstMember: {
id: 1337
},
secondMember: {
id: -1
}
};

var instance = new ExampleClass().deserialize(json);
console.log(instance);


Related Topics



Leave a reply



Submit