Class wwJsonSerializer

This class can serialize FoxPro objects, values, collections and cursors to JSON and deserialize JSON strings into FoxPro objects, values or collections. Arrays are supported only as members of objects - all lists should be expressed preferably as Collections.

The serializer supports complex nested structures and can also serialize (but not directly deserialize) FoxPro tables and cursors by way of a custom string syntax (cursor:AliasName). Objects returned are created dynamically on the fly with all arrays parsed into FoxPro collections.

The serializer can handle these FoxPro types:

  • Simple Values (strings, numbers, bools, dates, blob)
  • Objects - plain or nested
  • Arrays
  • Collections
  • Cursors (using special cursor:<cursorName> syntax)

The de-serializer can handle these JSON types:

  • Simple Values (string, number, bool, date, blob)
  • Objects - plain or nested
  • Arrays - returned as FoxPro Collections!
  • Cursors - returned as collections - use CollectionToCursor()

Serialization

Serialization takes a FoxPro value, object, collection or cursor and turns it into JSON.

Serialize Existing Objects or Values

do wwJsonSerializer
loSer = CREATEOBJECT("wwJsonSerializer")
lcJson = loSer.Serialize(loObject)  && Objects, Values, Collections

Simple Value Serialization

loSer = CREATEOBJECT("wwJsonSerializer")

*** Strings are encoded for a number of things which is why it's not a good
*** good idea to generate JSON by hand!
lcJson = loSer.Serialize("Some Long String."+ CHR(13) +CHR(10) + "More Text")
*** Result: "Some Long String.\r\nMore Text"

lcJson = loSer.Serialize(DateTime())  && "2024-01-23T22:44:10Z"
lcJson = loSer.Serialize(.T.)  && true
lcJson = loSer.Serialize(155.55) && 155.55

Simple Object Serialization

You can serialize any FoxPro objects or arrays, but you will end up with a lot of extra 'noise' in the object graph with types of Custom or Relation even, due to FoxPro base class properties which also get serialized.

To get clean objects that only hold the properties you create, you can use EMPTY objects and explicitly add properties which is what the following examples demonstrate.

loCustomer = CREATEOBJECT("Empty")
ADDPROPERTY(loCustomer,"lastname", "Strahl")
ADDPROPERTY(loCustomer,"firstname", "Rick")
ADDPROPERTY(loCustomer,"company", "West Wind")

loSer = CREATEOBJECT("wwJsonSerializer")
loSer.PropertyNameOverrides = "lastName,firstName"  && exact casing
lcJson = loSer.Serialize(loCustomer, .T.)

which produces:

{
  "company": "West Wind",
  "firstName": "Rick",
  "lastName": "Strahl",
}

Nested Objects and Arrays

loCustomer = CREATEOBJECT("Empty")

*** Top level properties
ADDPROPERTY(loCustomer,"lastname", "Strahl")
ADDPROPERTY(loCustomer,"firstname", "Rick")
ADDPROPERTY(loCustomer,"company", "West Wind")

*** Nested object
loAddress = CREATEOBJECT("EMPTY")  
ADDPROPERTY(loCustomer, "address", loAddress)  && Add as child to ^customer
ADDPROPERTY(loAddress, "street", "101 Nowhere Lane")
ADDPROPERTY(loAddress, "city", "Anytown")
ADDPROPERTY(loAddress, "zip", "11111")

*** Nested JSON Array (as FoxPro Collection)
loOrders = CREATEOBJECT("Collection")
ADDPROPERTY(loCustomer,"Orders",loOrders)

*** Add items to Array
loOrder = CREATEOBJECT("EMPTY")
ADDPROPERTY(loOrder,"OrderNo","11111")
ADDPROPERTY(loOrder,"Amount",150.40)
ADDPROPERTY(loOrder,"OrderDate",DATETIME())
loOrders.Add(loOrder)

loOrder = CREATEOBJECT("EMPTY")
ADDPROPERTY(loOrder,"OrderNo","2222")
ADDPROPERTY(loOrder,"Amount",50.55)
ADDPROPERTY(loOrder,"OrderDate",DATETIME())
loOrders.Add(loOrder)

*** A Collection doesn't have to have object members
*** It can also hold simple Values (ie. strings, numbers, dates etc.)
* loOrders.Add("Item 1")
* loOrders.Add("Item 2")

loSer = CREATEOBJECT("wwJsonSerializer")
loSer.PropertyNameOverrides = "lastName,firstName,orderNo,orderDate"  && exact casing
lcJson = loSer.Serialize(loCustomer, .T.)

Important: FoxPro objects don't return property names in a case sensitive way, so by default all property names are serialized as lower case. If you need mixed case property names, you can use PropertyNameOverrides to override specific property names with custom casing.

The above produces:

{
  "address": {
    "city": "Anytown",
    "street": "101 Nowhere Lane",
    "zip": "11111"
  },
  "company": "West Wind",
  "firstName": "Rick",
  "lastName": "Strahl",
  "orders": [
    {
      "amount": 150.4,
      "orderDate": "2024-01-23T22:31:17Z",
      "orderNo": "11111"
    },
    {
      "amount": 50.55,
      "orderDate": "2024-01-23T22:31:17Z",
      "orderNo": "2222"
    }
  ]
}

FoxPro Cursors or Tables

select * from customers into TCustomers
lcJson = loSer.Serialize("cursor:TCustomers")  && Tables/Cursors

Add a FoxPro Cursor as a nested Array

You can also add a cursor as a nested array property in the JSON:

loCustomer = CREATEOBJECT("Empty")

*** regular value property
ADDPROPERTY(loCustomer,"company", "West Wind")

*** Array property from open Cursor TOrders
select * from Orders where custId = lnOrderId INTO CURSOR TOrders
ADDPROPERTY(loCustomer,"orders","cursor:TOrders")   && creates JSON Array

Cursor Serialization

A Cursor is serialized into JSON as an Array of Objects and de-serialized from JSON as a FoxPro Collections of Objects. It does not automatically de-serialize back into a cursor!

However, you can use wwJsonSerializer::DeserializeCursor or CollectionToCursor() to convert JSON collections back into an open cursor or table.

Deserialization

Deserialization takes a JSON string and turns it into a FoxPro value (for simple types) or an object or collection, depending on the top level JSON structure.

Deserialization Formats

  • JSON Values are mapped to FoxPro Values (ie. string, numbers, bool, dates, bytes, binary)
  • JSON Objects are mapped to FoxPro EMPTY Objects
  • JSON Arrays are mapped to FoxPro Collection Objects
  • Nested object and array structures are maintained in the generated FoxPro object

The deserializer creates a mapped FoxPro object using values, objects and arrays that map the JSON structure into a FoxPro structure. This allows for objects and arrays to contain other objects and arrays in deeply nested structures.

Object Deserialization

Here's an example that demonstrates how wwJsonSerializer works when round-tripping data:

*** Start with a valid JSON string of a nested object
lcJson = '{ "id": 50445, "message": "Whooo hooo", ' +;
         '   "status": 10 , "entered": "2018-11-21T23:10:10Z", ' +;
         '   "address": { "street": "123 Timber Ln", "number": 32 } }'

*** Deserialize into a FoxPro object
loSer = CREATEOBJECT("wwJsonSerializer")
loResult = loSer.DeserializeJson(lcJson)

? loResult.Id       && 50444
? loResult.entered  && 11/21/2018 03:10 PM (local time)

*** turn the object back into JSON - formatted in this case
? loSer.Serialize(loResult,.T.)

The code takes in a JSON string converts it to an object and then turns around and turns it back into formatted JSON which looks like this (formatted because of the .T. parameter):

{
  "address": {
    "number": 32,
    "street": "123 Timber Ln"
  },
  "entered": "2018-11-21T23:10:10Z",
  "id": 50445,
  "message": "Whooo hooo",
  "status": 10
}

Arrays Deserialize as Collections

Arrays in JSON are deserialized as FoxPro Collection objects so they can be easily iterated using standard Collection syntax.

TEXT TO lcJson
[
  {
    "name": "John Doe",
    "company": "Acme Inc",
    "address": {
      "street": "123 Timber Ln"
    },    
    "status": 10
  },
  {
    "name": "Dianne Franken",
    "company": "Dole Inc",
    "address": {
      "street": "312 Northrupp"
    },    
    "status": 11
  }
]
ENDTEXT

LOCAL loItems as Collection

loSer = CREATEOBJECT("wwJsonSerializer")
loItems = loSer.Deserialize(lcJson)

FOR EACH loItem IN loItems FOXOBJECT
   ? loItem.Name
   ? loItem.Address.Street
   ? "----"
ENDFOR

*** Alternately access individual items by index
FOR lnX = 1 TO loItems.Count
	loItem = loItems[lnX]
	? loItem.Name
    ? loItem.Address.Street
    ? "----"
ENDFOR

*** Or this syntax
loItem2  = loItems.Item(2)
? loItem2.Name

Nested Arrays

You can access nested arrays inside of an object in the same way as top level arrays shown in the previous example:

TEXT TO lcJson
{
  "Customers": [
    {
      "name": "John Doe",
      "company": "Acme Inc",    
    },
    {
      "name": "Dianne Franken",
      "company": "Dole Inc",
    }
  ],
  "created": "2014-02-20T00:00:00Z"
}
ENDTEXT

LOCAL loCustomers as Collection

loSer = CREATEOBJECT("wwJsonSerializer")
loResult = loSer.Deserialize(lcJson)  && loResult is an object

loCustomers = loResult.Customers && Array Property

FOR EACH loCustomer IN loCustomers FOXOBJECT
   ? loCustomer.Name
   ? loCustomer.Company
   ? "----"
ENDFOR

*** Or directly
? loResult.Customers[1].Name
Custom
  wwJsonSerializer

Class Members

MemberDescription

Deserialize

Deserializes a JSON string into a FoxPro value, object or wwCollection instance for arrays. This method uses .NET and the JSON.NET library.

Objects are created as new instances of EMPTY objects and passed back. Arrays are passed back as FoxPro Collection objects.

o.Deserialize(lcJson)

DeserializeCursor

o.DeserializeCursor(lcJson, lcReadWriteCursor)

FormatJson

o.FormatJson(lcJson)

MapPropertyName

o.MapPropertyName(@lcJson, lcOriginal,lcNewName)

Property

o.Property(loObject,lcProperty,lvValue)

Serialize

o.Serialize(lvValue, llFormat)

AssumeUtcDates

If set to .T. assumes that all dates in the data structure passed are UTC dates and the dates are not time adjusted for UTC time.

DefaultEmptyDate

FormattedOutput

When .T. causes the output to be pretty formatted with indentations for each object level.

This flag affects the behavior of the Serialize() method.

IgnoreDollarVars

PropertyExclusionList

PropertyNameCharacterFilter

Property name filter applied to properties that are dynamically created by the deserializer. Strips out invalid characters for FoxPro variable names.

Defaults to all alpha-numeric characters and _.

This property is internally set and generally you shouldn't mess with this value.

PropertyNameOverrides

TrimStringValues

If .T. trims all string values removing trailing spaces to minimize payload size on the wire.

Requirements

Assembly: wwjsonserializer.prg wwDotnetBridge.prg wwutils.prg wwapi.prg wwcollections.prg wwDotnetBridge.dll newtonsoft.json.dll

See also:

Class wwDynamic

© West Wind Technologies, 1996-2024 • Updated: 02/18/24
Comment or report problem with topic