So I've been looking to convert my Json array into an array of objects in Unity. I've found my solution from a 2 yeard old thread without explanation but I'm curious as to how it actually works.
If I use Visual Studio to look for the definition of FromJson it shows me this
public static T FromJson<T>(string json);
As I understand is that FromJson asks for an object to be filled, I give the class MyWrapper but besides telling MyWrapper that he contains a list of Question I never ask it to create a new item in the list. So how does it actually fill the list?
C#
MyWrapper wrappedQuestions = JsonUtility.FromJson<MyWrapper>(jsonString);
[Serializable]
public class MyWrapper
{
public List<Question> questions;
}
[Serializable]
public class Question
{
public int questionType;
public string questionString;
public int questionAnswer;
}
Json
{
"questions": [
{
"questionType": 1,
"questionString": "4^2",
"questionAnswer": 16
},
{
"questionType": 2,
"questionString": "√(25)",
"questionAnswer": 5
}
]
}
I'm still a beginner programmer so I hope I'am able to ask such questions here.
If you wonder why you need a wrapper for that, that's simply because Unity engineers did not add direct support for primitive types or arrays. It's just how they programmed it. Most Json API are not like this.
So how does it actually fill the list?
Reflection.
1.It reads the json you passed to it. It detects the questions variable in the json. It detects that the questions variable is an array due to the format such as [] and the commas that separates each item.
2.It finds the type of that questions variable which is Question or List of Question.
3.It uses Activator.CreateInstance to create new instance of Question, read each value from each variable in the json and then fill them up with reflection with that new instance it created.
4. It returns the new instance that is filled.
If you read and understand how to do basic stuff with reflection in C#, you may be able to make your own simple Json parser with the Queue class.
Finally, you can use the JsonHelper wrapper from this answer to serialize/de-serialize arrays easily without having to make a wrapper for each class.
As JSON stands for Javascript Object Notation, JSON objects (strings) follow a pattern. For any string to parsed and converted to the object, it has to be a valid JSON. It has certain rules/syntax,
for example
[ ] is for array/list,
{ } is for objects
and every object can contain key-value pairs (separated by colon :) to represent variables along with their values
{ "Key" : "Value" }
Now JSON parser is aware of these rules so it can check if any string is valid JSON.
What does it need to know to convert a JSON into a class object?
The class type you provide here :
JsonUtility.FromJson<MyWrapper>(jsonString);
is MyWrapper.
It should have the same structure as your jsonString.
Let's break down your jsonString to map it with the class structure:
This represents a MyWrapper object. which contains only one property called questions, which is an empty list.
{
"questions": [ ]
}
if questions have any element in it, it would be of type Question we can write it as following in JSON:
{
"questionType": 1, // int value
"questionString": "4^2", // string value
"questionAnswer": 16 // int value
}
now if questions have more than one elements they would be separated by a comma:
"questions": [
{
"questionType": 1,
"questionString": "4^2",
"questionAnswer": 16
},
{
"questionType": 2,
"questionString": "√(25)",
"questionAnswer": 4
},
...
]
JSON parser is aware of all these rules so by looking at class structure it can parse the string and create an object of the stated class provided that JSON is valid and class structure matches with JSON structure.
That's all I could say about this, hope you got the basic understanding.
Related
So there's no confusion, when I talk through my issue I am doing so as someone who is using the compiled classes that result from Bond schemas (that is to say I use "class" instead of "struct", etc.). I feel like it makes more cognitive sense to think of it this way.
I am using Microsoft Bond and I have a main class that has several properties, one of which is an instance of a derived class.
When creating an instance of the main class I have no problem setting the property to an instance of the derived class; however when I deserialize from binary back into the main class the property is now seen as its base class.
I have tried to cast it as the derived class but that throws a runtime exception.
The examples for using derived classes in the Bond documentation/manual have you specifying the derived class at the time of deserialization, but I am not deserializing just the derived class but the main class.
Here's an example of how I have the bond schema set up
struct BaseExample
{
0: int property1;
}
struct DerivedExample : BaseExample
{
0: int property2;
}
struct MainExample
{
0: BaseExample mainProperty;
}
In usage I am setting mainProperty to an instance of the DerivedExample class.
What I'd expect is that after deserialization, mainProperty is still of type DerivedExample (containing property2) but what I am seeing instead is mainProperty is of type BaseExample (and doesn't contain property2)
Am I forced to use generics to do this or is there something I am missing?
EDIT: Adding examples
My code that uses the classes generated from the Bond schemas is like this.
We have a calling service that creates a message of this type and uses Bond to serialize it into a byte array before sending it on a stream.
var message = new MainExample();
var derivedExample = new DerivedExample()
{
property1 = 1,
property2 = 2,
};
message.mainProperty = derivedExample;
// This block is all from the Bond examples
var output = new OutputBuffer();
var writer = new CompactBinaryWriter<OutputBuffer>(output);
Serialize.To(writer, message);
SendMessage(output.Data.Array);
Now we have a receiving service that is going to take this message off the stream and use Bond to deserialize it back into an object.
void HandleMessage(byte[] messageBA)
{
// This block is all from the Bond examples
var input = new InputBuffer(messageBA);
var reader = new CompactBinaryReader<InputBuffer>(input);
MainExample message = Deserialize<BondEvent>.From(reader);
// mainProperty is now of type BaseExample and not DerivedExample
message.mainProperty.property1; // is accessable
message.mainProperty.property2; // will not compile
DerivedExample castedProperty = message.mainProperty as DerivedExample; // fails at runtime
}
Full disclosure: I am actually using F# but I figured it would be better to do these in C#
The slicing behavior that you are observing when deserializing is expected with the schemas as written. The MainExample.mainProperty field is of type BaseExample, so when it is serialized, only the BaseExample fields are written. It doesn't matter which runtime type is used. Additionally, when it is deserialized, only BaseExample fields will be realized.
When dealing with inheritance and polymorphism, Bond does not include any type information in serialized payloads: it leaves the decision about how to model this up to the schema designer. This stems from Bond's philosophy of only paying for what you use.
Depending on the data you are modeling, I see two ways to design your schema:
generics
bonded
Generics
As mentioned in the question, the MainExample struct could be made generic:
struct MainExample<T>
{
0: T mainProperty;
}
This essentially allows you to easily create a bunch of different structs with similar shapes. But these structs will not have an "is a" relationship. Methods like HandleMessage likely will also have to be generic, causing a generic cascade.
Bonded
To include a field in another struct that is polymorphic in type, make the field a bonded field. Bonded fields do not slice when serialized. Also, they are not immediately deserialized, so the receiving side has a chance to pick the appropriate type to deserialize into.
In the .bond file, we'd have this:
struct MainExample
{
0: bonded<BaseExample> mainProperty;
}
To serialize, the following:
var message = new MainExample();
var derivedExample = new DerivedExample()
{
property1 = 1,
property2 = 2,
};
message.mainProperty = new Bonded<DerivedExample>(derivedExample);
// NB: new Bonded<BaseExample>(derivedExample) WILL slice
And to deserialize:
void HandleMessage(byte[] messageBA)
{
// This block is all from the Bond examples
var input = new InputBuffer(messageBA);
var reader = new CompactBinaryReader<InputBuffer>(input);
MainExample message = Deserialize<BondEvent>.From(reader);
DerivedExample de = message.mainProperty.Deserialize<DerivedExample>();
}
When using bonded fields for polymorphism, we will need to have some way of knowing which most-derived type to deserialize into. Sometimes this is known from context external to the payload (e.g., perhaps each of the messages handled only has one type). Other times, we need to embed this information in the common part of the payload. A common way to do this is with an enum:
enum PropertyKind
{
Base;
Derived;
}
struct MainExample
{
0: bonded<BaseExample> mainProperty;
1: PropertyKind mainPropertyKind = Base;
}
There's a fully worked example of doing this kind of dispatch in the C# polymorphic_container example in the Bond repository.
OutputBuffer.Data.Array
I notice that in the code to send a message, there's the following line, which contains a bug:
SendMessage(output.Data.Array);
The OutputBuffer.Data property is an ArraySegment<byte>, which is used to represent a slice of some other array. This slice may be shorter than the entire array (the Count property), and it may start at an offset other than 0 (the Offset property). Most I/O libraries have an overload like SendMessage(byte[] buf, int offset, int count) that can be used in cases like this.
The default array backing an OutputBuffer is 65K, so there's almost certainly a bunch of extra data being sent.
I have some JSon that I am converting to an object using the ToObject method.
A part of this Json has a repeated element which is correctly represented as an array in the Json text. When I convert this it correctly is mapped to the C# object
public IList<FooData> Foo { get; set; }
But when I only have 1 element I get an error saying that the Json that I am trying to Parse into an object is not an array because it does not have [] around it.
Does Json.NET support single element arrays?
But when I only have 1 element I get an error saying that the Json
that I am trying to Parse into an object is not an array because it
does not have [] around it.
If a JSON text has no [] around, then it's not a single-element array: actually it's an object (for example: { "text": "hello world" }).
Try using JsonConvert.DeserializeObject method:
jsonText = jsonText.Trim();
// If your JSON string starts with [, it's an array...
if(jsonText.StartsWith("["))
{
var array = JsonConvert.DeserializeObject<IEnumerable<string>>(jsonText);
}
else // Otherwise, it's an object...
{
var someObject = JsonConvert.DeserializeObject<YourClass>(jsonText);
}
It can also happen that JSON text contains a literal value like 1 or "hello world"... but I believe that these are very edge cases...
For the above edge cases just deserialize them with JsonConvert.DeserializeObject<string>(jsonText) for example (replace string with int or whatever...).
Make sure you are enclosing your JSON single item array is still specified as an array using array notation []
Consider the following JSON object:
{
"value": 0
}
Now suppose I'm mapping this to a .NET type Foo:
class Foo
{
public double Value { get; set; }
}
The type of Foo.Value is double, because Value isn't always an integer value.
Using JSON.NET, this works beautifully:
Foo deserialized = JsonConvert.DeserializeObject<Foo>(json);
However, observe what happens when I try to convert the object back to its JSON representation:
string serialized = JsonConvert.SerializeObject(deserialized, Formatting.Indented);
Output:
{
"Value": 0.0
}
Notice the trailing zero? How do I get rid of it?
EDIT
I suspect that the answer will be write your own converter. If it is, then that's fine and I guess I'll accept that as the answer. I'm just wondering if perhaps there exists an attribute that I don't know of that lets you specify the output format (or similar).
It appears that this is a hard-coded behavior of the library:
https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonConvert.cs#L300
If you want to alter the behavior you'll need to edit the library and recompile from source (or choose another JSON library)
I am attempting to take an object structured like so
public class Item
{
public Guid SourceTypeID {get;set;}
public Guid BrokerID {get;set;}
public double Latitude {get;set;}
public double Longitude {get;set;}
public DateTime TimeStamp {get;set;}
public object Payload {get;set;}
}
and serialize it with JSON.NET using a call like:
Item expected = new Item()
{
SourceTypeID = Guid.NewGuid(),
BrokerID = Guid.NewGuid(),
Latitude = 33.657145,
Longitude = -117.766684,
TimeStamp = DateTime.Now,
Payload = new byte[]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
};
string jsonString = JsonConvert.SerializeObject(expected);
The payload member of the Item object will potentially hold any one primitive of a list of C# primitives (plus a few others like Guid), or an array of those types (as in the example, a byte array), or a "flat" object composed of any of those previously listed "primitives" (dynamically created).
When I perform the SerializeObject() call, the string that is produced contains:
{"Payload":"AAECAwQFBgcICQ==","SourceTypeID":"d8220a4b-75b1-4b7a-8112-b7bdae956a45",
"BrokerID":"951663c4-924e-4c86-a57a-7ed737501dbd",
"TimeStamp":"\/Date(1328202421559-0600)\/",
"Latitude":33.657145,"Longitude":-117.766684}
However when I make the deserializing call the item that is produced is partially incorrect
Item actual = JsonConvert.DeserializeObject<Item>(jsonString);
actual.SourceTypeID : {00000000-0000-0000-0000-000000000000}
actual.BrokerID : {951663c4-924e-4c86-a57a-7ed737501dbd}
actual.Latitude : 33.657145;
actual.Longitude : -117.766684;
actual.TimeStamp : {2/2/2012 11:07:01 AM}
actual.Payload : null
The SourceTypeID member (Guid), and the Payload member (object, holding a byte[]), are both incorrect. The serialized string seems to hold the right identity for the guid, but not for the byte array.
I see that an alternate signatureof SerializeObject is
SerializeObject(object value, params JsonConverter[] converters);
Is this the correct way to inform the de/serialization engine about types that it apparently handles wrong? Since I am working with a limited set of "primitives" at the core of my object, if I could create a set of converters for each of those types, would that solve my problem?
I want to avoid reinventing the wheel if possible, as it seems that this would be a problem that has already been handled gracefully, and I'd like to leverage something like that if available.
Payload will be deserialized as a string unless you place a [JsonProperty] attribute with TypeNameHandling enabled on it, otherwise the deserializer won't know what to deserialize it as.
I haven't been able to duplicate the problem you got with some properties being null using the latest source code at http://json.codeplex.com/SourceControl/list/changesets
This appears to be an actual bug in JSON.NET.
It's not idea, but a workaround might be to have a two-stage serialization. The first, the object that actually gets serialized to/from JSON, would consist only of fields/properties with string type. The second object (the one your server-side code would use) would be an object that is converted to strong types manually.
Not ideal, but possible. Additonally, you could consider using the DataContractJsonSerializer, which does a pretty good job of handling byte arrays and guids IME. Dates are still pretty sucky, but that's mostly the fault of Javascript.
EDITED: Updated 3/23/09. See rest of post at bottom. I'm still having trouble with the indexer. Anymore help or examples would really help me out.
Write a class, MyCourses, that contains an enumeration of all the
courses that you are currently taking.
This enum should be nested inside of
your class MyCourses. Your class
should also have an array field that
provides a short description (as a
String) of each of your courses. Write
an indexer that takes one of your
enumerated courses as an index and
returns the String description of the
course.
Write a class MyFriends that contains an indexer that provides
access to the names of your friends.
namespace IT274_Unit4Project
{
public class MyCourses
{
// enumeration that contains an enumeration of all the courses that
// student is currently enrolled in
public enum CourseName {IT274= 0,CS210 = 1}
// array field that provides short description for each of classes,
// returns string description of the course
private String[] courseDescription =
{"Intermediate C#: Teaches intermediate elements of C# programming and software design",
"Career Development Strategies: Teaches principles for career progression, resume preparation, and overall self anaylsis"};
// indexer that takes one of the enumerated courses as an index
// and returns the String description of the course
public String this[CourseName index]
{
get
{
if (index == 1)
return courseDescription[0];
else
return courseDescription[1];
}
set
{
if (index == 1)
courseDescription[0] = value;
else
courseDescription[1] = value;
}
}
}
}//end public class MyCourses
I'm working on this homework project and having trouble understanding the text explaining how to correctly take the accessed value of the enumeration and then apply the string array value to it. Can you please help me understand this? The text we are using is very difficult and poorly written for a beginner to understand, so I'm kind of on my own here. I've got the first parts written, but need some help on the accessing of the enumeration value and assigning, i think i'm close, but don't understand how to properly get and set the values on this.
Please do not provide me with direct code answers, unless a MSDN style explanation that is generalized and not specific to my project. ie:
public class MyClass
{ string field1;
string field2;
//properties
public string Value1
get etc...
Thanks!
First of all, the base type of an enumeration has to be a numeric value type, so you can't have an enumeration with base type string. The following isn't going to compile:
public enum CourseName
{
Class1 = "IT274-01AU: Intermediate C#",
Class2 = "CS210-06AU: Career Development Strategies"
}
So change it to use the default base type of int. Something like the following will do, but change the names as you see fit (you might want to use the course name instead of the code, for example). Remember also that you should use meaningful names whenever possible in an enumeration.
public enum Courses
{
IT274_01AU,
CS210_06AU
}
(I know you said you didn't want specific code examples, but I think this one illustrates my point much more clearly than any explanation.)
Second, you're on the right track with the indexer, but you have to think of how to relate the enumeration to the array of string descriptions. Remember, an enumeration is nothing more than a finite set of glorified (named) numbers. With the above Courses enumeration, you have two values named IT274_01AU and CS210_06AU. So in the indexer, you have to map each of these values to the string description. There are multiple ways to do it, but the simplest one would be a switch statement, for example:
switch (myEnum)
{
case value1:
return string1;
case value2:
return string2;
}
Another option, however is to explicitly map the enum values to its base type, and use the base type to index into your array. For example, if you have the enum
public enum Enumerations
{
value1 = 0,
value2 = 1
}
then you can index directly into an array using myArray[(int)myEnum]. This may be of use and is the slightly-more-advanced-but-less-lines-of-code-and-arguably-easier-to-understand method.
(resisting the urge to write code)
First off, an enumeration is a named list of integers and (per MSDN) the approved types for an enum are byte, sbyte, short, ushort, int, uint, long, or ulong.
Also, remember that courseDescription is an array of strings and the purpose of the indexer is to give you an index into that array (ie. [0] returns the first string, [1] returns the second, etc.).