I really love the way mORMot / Synopse has implemented the handling of JSON, especially the way you can use the JSON elements in your code (like MyString := myjson.name). This is very intuitive and useful in wrapping objects which only have one Variant (JSON) with state which we access through getters/setters, like below:
TMyObject = class
private
FState: Variant;
function GetName: String;
public
constructor Create(AJson: Variant);
property Name: String read GetName;
end;
function TMyObject.GetName: String;
begin
Result := FState.name;
end;
It is really powerful, but I would like to get the 'default' variant value if an element is not found in the corresponding JSON document (so, above example should return an empty string if 'name' is not present).
I don't want to use NullStrictConvert, because that is not thread safe and influences the rest of our program. Of course I could check VarIsNull(FState.name), but then I have to do this for each element and I prefer not to have this extra boilerplate.
Any suggestions?
When you unserialize some JSON into an object, the missing fields are left untouched IIRC.
So you can just set the fields to their default values before unserializing the JSON.
One way of doing it is to inherit from TSynPersistent and override the Create constructor and set default value(s).
Edit: You may use a TDocVariantData instead of variant, and call GetAsRawUTF8() and such methods which return false if the property is not existing.
Related
I would like to use the REST.JSON Librairy to convert an Object to a JSON-String and back, but i'm encountering some problems.
Let's say the classes i would like to convert is "TFooChild1" and "TFooChild2" which both descend from "TFoo".
The classes look like this:
TFoo = class
protected
Name: string;
Value: Double;
end;
TFooChild1 = class(TFoo)
private
Limit: Double;
end;
TFooChild2 = class(TFoo)
private
Limit: Double;
WorkerID: Integer;
end;
Creating and converting to JSON would look something like this:
var
Foo: TFoo;
s: string;
begin
Foo := TFooChild1.Create;
Foo.Name:= '...';
... //assign all the Fields
s := TJson.ObjectToJsonString(Foo);
Foo.Free;
//conversion to string is correct...
Foo := TJson.JsonToObject<TFoo>(s, []);
//Field "Limit" is lost
end
I'm aware that this (TJson.JsonToObject<TFoo>(s, [])) would never return the type TFooChild1, but that's exactly what i need.
When you convert back to an Object, the Fields of the child classes are lost. How do I avoid this? I can't do JsonToObject<TFooChild1> because I don't know if it's gonna be Child1 or Child2.
Any hints?
I've googled if there's maybe a ConvertOption that would include Type information, but i haven't found anything
Add Rest.JsonReflect to your uses class as you will need to use the [JsonReflect(ctObject,ctObject,TFooInterceptor)] attribute attached to where you are referencing the TFoo class. Then you will have to code a TFooInterceptor object to intercept and handle the appropriate conversion. I would code the ObjectReverter and ObjectConverter overrides and start coding from there. Your TFooInterceptor should descend from TJSONInterceptor. Some examples are in the source REST.Json.Interceptors.pas.
I've searched a lot, but cannot find a working solution. Most likely because I'm pretty new to Delphi (and coming from C#).
I have a unit with the following class declaration:
TReplaceField = class
public
Key: string;
Value: string;
constructor Create; overload;
constructor Create(strKey, strValue: string); overload;
end;
Class can also be record, like suggested in the comment.
I want to create a collection of it, be it an array, TList<T>, or whatever suits best in this case, and then serialize it to JSON (to store it) and later deserialze it to loop over the fields.
I've found REST.JSON and TJson.ObjectToJsonString(), which works good for 1 object.
But if I pass an array of TReplaceField, it gives me a compile error:
E2010 Incompatible types: 'TObject' and 'Dynamic array'
When I use a TList at runtime, I get a different error:
type tkPointer is not currently supported
In C#, all of this is pretty easy, like a few lines of code. I'm pretty stuck on how to do it in Delphi (besides creating JSON by hand via string concatenation).
UPDATE:
With the tips from David, I've got the serialization working. Also deserialization of one object works, but not deserialization of a collection.
My class now looks like this:
TReplaceField = class
private
FKey : string;
FValue : string;
published
property Key : string read FKey write FKey;
property Value : string read FValue write FValue;
public
constructor Create; overload;
constructor Create(strKey, strValue: string); overload;
end;
Serialize one object: mmoJson.Lines.Text := TJson.ObjectToJsonString(Field); OK
Deserialize one object: JsonToObject<TReplaceField>(mmoJson.Lines.Text); OK
Serialize collection: mmoJson.Lines.Text := TJson.ObjectToJsonString(FieldList); OK
Deserialize collection: JsonToObject<TReplaceField>(mmoJson.Lines.Text); FAIL, message:
EConversionError with message 'Internal: Cannot instantiate type System.Generics.Collections.TList<uReplaceField.TReplaceFieldRec>'
Using a record instead of class for ReplaceField gives the same results.
Almost there. What am I missing to get it fully working?
type
TReplaceCollect = TList<TReplaceField>;
Add this after-class declaration.
I have joined Countries to Locations in the HR Sample database in OracleXE.
I am then using a tMap to generate a nested JSON document.
it works, but for some reason null values in the Location are coming through as arrays in the final output in the console (have also tried MongoDB).
Because tWriteJSONField generates an xml, then converts it to JSON using json-lib. Your null value will be converted to an empty xml node <STATE_PROVINCE/>, and json-lib, having no context of this node, will assume it is a parent node with no children, instead of an empty text (null notion is already far at this point).
Here is what happens in short:
package test.json;
public class JSONTest {
public static void main(String[] args) {
net.sf.json.xml.XMLSerializer s = new net.sf.json.xml.XMLSerializer();
s.clearNamespaces();
s.setSkipNamespaces(true);
s.setForceTopLevelObject(true);
net.sf.json.JSON json = s.read("<?xml version=\"1.0\" encoding=\"ISO-8859-15\"?>" +
"<org>" +
"<STATE_PROVINCE/>" +
"</org>"
);
System.out.println(json.toString());
}
}
Result:
{"org":{"STATE_PROVINCE":[]}}
A dirty solution is to use attributes instead of nodes in your tWriteJSONField, but it will prefix your properties with #. So after this component you put a tReplace, search "\"#", replace with "\"", uncheck whole word, check global expression. Your final JSON will have no property if null.
Thanks to https://www.talendforge.org/forum/viewtopic.php?id=27791
Insert a tJavaRow with the following code right after your tWriteJsonField:
output_row.output = input_row.output.replaceAll(",?\"[a-zA-Z_0-9]*\":\\[\\]", "");
I believe the elegant (and short) solution would be the following.
Talend docs state that:
When configuring a JSON tree, the default type of an element is
string. If an element is not of type string, you need to add an
attribute for the element to set its type.
So, to the object receiving a null value, you should add an attribute named class and set its static value to object.
Pic: JSON Tree Configuration
And voilĂ !
PIC: "Complemento":null
Im reading the tutorials here: http://www.adobe.com/devnet/actionscript/learning/oop-concepts/objects-and-classes.html and an on the second paragraph of the Dot Notation section. It uses the 'Sprite' class in ActionScript 3. The tutorial created an instance of the Sprite class and called it myFirstObject. It says..
"Then, using that reference variable and dot notation, values are assigned to the x and visible properties of the instance, and the methods startDrag and stopDrag are called."
I noticed that there are no () after a property. For example:
myFirstObject.x = 300;
compared to a method
myFirstObject.startDrag();
So, what's the difference between a property and method of an instance? I think it would help if I can see the Sprite class but I wasn't able to find it when I tried google'ing.
A property has a Get() and Set() method that allow you to use the same call to get or assign a value. When you assign the property a value, you are calling the Set method. When you retrieve a value, you are using the Get method. Properties automatically call the appropriate Get or Set method based on the operation.
To help you visualize the setup, here is a sample property (VB.Net):
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Private Set(ByVal value As String)
_name = value
End Set
End Property
To call it, you would use:
MyObject.Name = "Test" <- Sets the name to test
MsgBox("The name is: " & MyObject.Name) <- Gets the value of name
Although the example is in VB.Net, the theory is still the same.
A method, on the other hand, would be the equivalent of either the Get or Set routines. As a method, you have to call it and supply the parameters inside of the parenthesis. Even if it has none, you still need the (). When you want to update a variable, you have to pass values to the method instead of setting it equal to the value.
Here is a similar example:
Private _name As String
Public Function Name(Optional ByVal strName as String = "") as String
If strName <> "" then
_name = strName
End If
Return _name
End Function
Here is a similar example of how to use it:
MyObject.Name("Test") <- Sets the name to test
MsgBox("The name is: " & MyObject.Name()) <- Gets the value of name
Properties and methods are similar in that both are implemented as procedures that accept arguments. In general, properties store data for an object, and methods are actions an object can be asked to perform.
I have a object with some TObjectList<>-fields that I try to encode as JSON with help form SuperObject.
TLogs = TObjectList<TLog>;
TMyObject = class(TObject)
private
FLogs: TLogs;
end;
Deep inside SuperObjects code, there is a ToClass procedure, iterating the fields and add them to the json result.
In this loop, there is a check on the TRttiFields FieldType. If it's nil, it skips the object.
for f in Context.GetType(Value.AsObject.ClassType).GetFields do
if f.FieldType <> nil then
begin
v := f.GetValue(value.AsObject);
result.AsObject[GetFieldName(f)] := ToJson(v, index);
end
My generic list fields have a FieldType of nil. Why?
How can I make SuperObject serialize my list of objects?
This is a known issue in Delphi's RTTI creation. If you declare your generic class like that, it won't work. You need to use the class keyword.
TLogs = class(TObjectList<TLog>);
Hopefully this will be fixed in the next release.