Skip to content

Dynamic Content | In Depth

Schematic

There are four components to keep in mind as we use Joystick Dynamic Content. We will discuss each of these in detail.

  1. The request from your application.
  2. The config that is stored in Joystick.
  3. The optional Dynamic Mapping that can be enabled for each config.
  4. The response back to your application.

Declaring Content for Segmentation or AB Testing

The first step is to declare the content you would like to be dynamic for segmentation or AB Test. Declarations are made directly in your config JSON using a simple syntax.

Declaration Syntax

The Joystick Link Key

The Joystick Link Key is what makes our system powerful. You declare using a simple syntax any parameters directly in your content. When we send the JSON out over the API, the Link Keys are all scrubbed and replaced with either the default value, or the dynamic value based on segmentation or AB test matches.

Dynamic Link Key

Item Explanation
Pre-fix The prefix must be four characters "#LK-" (Link Key)
Link Key Name Used to map the response variables into the correct place in the configuration. This should be unique.
Data Type One of the six JSON standard data types: string, number, boolean, object, array.
Default Value The value to use if there is no match to a rule.
Post-fix The end of the string must be the hash symbol "#"

Declaring String, Number or Boolean

{
    "my_string": "#LK-SimpleString:string:Hello!#",
    "my_number": "#LK-SimpleNumber:number:12345#",
    "my_boolean": "#LK-SimpleBoolean:boolean:true#"
}

As soon as you enter the declarations, the editor in Joystick will recognize it, then show you the default values it parsed.

Declarations

Declaring Objects or Arrays as Dynamic Content

{
    "my_object": "#LK-SimpleObject:object:my_object#",
    "my_array": "#LK-SimpleArray:array:my_array#",
    "_dynamic_values_": {
        "my_object": {},
        "my_array": []
    }   
}

You can make entire objects or arrays also dynamic.

  1. Add a special object to the root of your content called _dynamic_values_. This is where the default values for arrays and objects be picked up from.
  2. The default value should be a key that corresponds to the array or object in _dynamic_values_.

    About _dynamic_values_

    The "_dynamic_values_" object will be removed when sending the response back to you. It will NOT appear when requesting your content over API.

The _dynamic_values_ object will be highlighted in the editor.

Declare Object

The Dynamic Content Map

When you have identified and declared the components you would like to segment or ab-test in your content, you can now create the Dynamic Map. The Dynamic Map is the rule-set that determines how your content is transformed.

Click Add Dynamic Map when you are editing a piece of content to open up the Dynamic Map Editor panel.

Add Dynamic

[
  {
    "expression": "location = 'ch'",
    "name": "offer_title_test",
    "info": "Optional, additional info.",
    "activationDateTime": {
      "start": "2022-01-01T01:01:01Z",
      "end": "2099-12-31T23:34:45Z",
      "overrideAndActivateNow": false
    },
    "groups": [
      {
        "variantName": "variant_a",
        "mod": [
          [ 0, 199 ]
        ],
        "values": {
          "LK-OfferTitle": "Extra Special Offer"
        }
      }
    ]
  }
]
Setup Description
expression REQUIRED An expression, if evaluated true, then our system will attempt to process with either segmentation or ab testing. The ncalc expression engine is used. The expression can use any of the parameters passed in on the "p" dictionary of your request. See below for details.
name REQUIRED A short string to identify this segment or ab-test.
info Optional A short string with any additional information you would like returned regarding this segment or ab-test.
activationDateTime Optional A dictionary that defines the date time range over which this segment is active. You can use this to schedule live-ops events.
activationDateTime.start activationDateTime.end If activationDateTime is used, either start or end is required. Optional to have both define. Start and end time, defined in ISO8601 format. Example: 2022-01-01T01:01:01Z. You may set just a start (event has no ending), just an end (event starts immediately), or both.
activationDateTime.overrideAndActivateNow Optional Useful for QA testing. If you have an event with a start date in the future, but you want the effects of the segment to activate right now for QA testing without having to change the start date-time, you can set this to true.
groups Either groups or values must be defined, but not both. An array of ab-test or treatment groups.
groups[].variantName Required if groups used. A string that is unique and allows you to identify a variant of the AB test.
groups[].mod Required if groups used. An array of arrays with two integers (0-999 inclusive) that represent the mod range over which to show this variation. See below for details.
groups[].values Required if groups used. A dictionary of key-value pairs, where the keys match the Link Keys declared in the configuration content. The values here will be used as replacements if the expression matches (evaluates as true) AND the user falls into one of the mod ranges defined.
values Either groups or values must be defined, but not both. A dictionary of key-value pairs, where the keys match the Link Keys declared in the configuration content. The values here will be used as replacements if the expression matches (evaluates as true).

Expression

The expression parameter makes Joystick Dynamic Content extremely versatile and flexible. On the p object in your request, you can send along different parameters, which can then be used in the expression.

  • When the expression evaluates to true then the content the content replacement of the group it is in will be processed.
  • You can have multiple groups that all evaluate to true. Content replacement is processed in priority from top to bottom.

Please Note

The expression, when fully resolved, should evaluate to true or false. Any errors mean the group will be ignored.

or, ||
and, &&
!, not, ~ (bitwise not)
=, ==, !=, <>
<, <=, >, >=
+, -, *, /, %
& (bitwise and), | (bitwise or), ^ (bitwise xor), << (left shift), >> (right shift)

Expression Example

{
    "u": "USER ID OR GUID",
    "p": {
        "myString": "hello",
        "myStringArray": ["one", "two", "three"],
        "myNumber": 10,
        "myNumberArray": [1, 2, 3] 
    }
}

Given that the body sent with the config is the same as above, here are some examples to show you how expressions work.

"expression": "myString == 'hello'" // true
"expression": "(myNumber * 2) == 10 " // false
"expression": "myString == 'hello' and myNumber >= 10" // true (Use logical operators to combine statements)
"expression": "StringIn('one', myStringArray) and myNumber < 20" // true (Check if 'one' is a member of myStringArray)
"expression": "NumberIn(6, myNumberArray)" // false
"expression": "in(myString, 'hello', 'goodbye')" // true (Is the first argument in hard-coded list)
"expression": "if(myNumber > 20, true, myString == 'goodbye')" // false (Using an if statement)

Controlling AB Testing or Staged Rollout using Mod

With Joystick you can control and place your users into stable ab test groups.

  • Based on the "u" parameter sent on the request body, we will generate a corresponding stable integer between 0 and 999 inclusive. The same "u" will always be transformed into the same integer.
  • To expose only a partial group of your audience to a variation, you can use groups[n].mod to define the ranges which will be given a particular treatment.
  • You define a 'band' of users to expose using a pair of integers between 0 and 999. The numbers are inclusive. Each increment of 1 represents 0.1% of your audience.

Examples with Explanation

"mod": [[0,0]] // This will expose 0.1% of your audience

"mod": [[500,599]] // This will expose 10% of your audience.

"mod": [[200,299], [800,899]] // This will expose two separate 'bands' of your audience for a total of 20%.

You can run multiple simultaneous AB tests by exposing different non-overlapping 'bands' of your audience. An example of this is shown below.

[
    {
        "expression": "1 = 1"
        // Other params not shown
        "groups": [
            {
                "mod":[ [200,249] ] // 5% test group.
                // Other params not shown
            }
        ]
    },
    {
        "expression": "1 = 1"
        // Other params not shown
        "groups": [
            {
                "mod":[ [700,799] ] // 10% test group.
                // Other params not shown
            }
        ]
    },
]

Dynamic Content: Extended Example and Explanation

This example demonstrates the functionality and flexibility of Joystick Dynamic Content. Both the Dynamic Map and Content are shown.

[
  {
    "expression": "location = 'ch'",
    "name": "offer_title_test",
    "info": "Optional, additional info.",
    "groups": [
      {
        "variantName": "variant_a",
        "mod": [
          [
            0,
            199
          ]
        ],
        "values": {
          "LK-OfferTitle": "Extra Special Offer"
        }
      },
      {
        "variantName": "variant_b",
        "mod": [
          [
            500,
            599
          ],
          [
            700,
            899
          ]
        ],
        "values": {
          "LK-OfferTitle": "A Very Good Offer!"
        }
      }
    ]
  },
  {
    "expression": "days_in_app >= 7",
    "name": "improved_offer",
    "info": "Optional, additional info.",
    "values": {
      "LK-Price": 2.99,
      "LK-BundleItems": { "coins": 100, "gear": ["hammer", "tent", "portable-stove"] }
    }    
  }
]
{
  "SpecialPricingOffer":
  {
    "OfferTitle": "#LK-OfferTitle:string:Special Offer#",
    "Price": "4.99",
    "BundleItems": "#LK-BundleItems:object:bundle_items#"
  },
  "_dynamic_values_":
  {
    "bundle_items":
    {
      "coins": 50,
      "gear": ["backpack"]
    }
  }
}

There are two segments defined in the Dynamic Map (two objects in the main array).

  1. "expression": "location = 'ch'"
    • This one is named "offer_title_test", and will match if the 'location' parameter passed in on the request is the string 'ch'.
    • There are two ab-test variants.
    • variant_a is exposed to 20% (mod range 0 to 199 inclusive) of the audience, and the title "Extra Special Offer" will be shown.
    • variant_a is exposed to 30% (mod range 500 to 599 and 700 to 899) of the audience, and the title "A Very Good Offer!" will be shown.
  2. "expression": "days_in_app >= 7"
    • This one is named "improved_offer", and will match if the 'days_in_app' parameter passed in on the request is a number equal or greater than 7.
    • There is no ab-test for this segment. When there is no ab-test, the "values" object can be placed at the same level as "expression".
    • There are two parameters to be replaced if this segment is matched.
      • LK-Price will be replaced with 2.99
      • LK-BundleItems will be replaced with a bigger bundle.
    • Notice how the groups parameter is not required if you have the values parameter.

Complete Example

Below is a complete example showing what each component looks like.

// This is the config that is stored in Joystick. It has a dynamic content declaration.
{
    "SpecialPricingOffer": {
        "OfferTitle": "#LK-OfferTitle:string:Special Offer#",
        "PercentBonus": "10",
        "Price": "4.99",
        "BackgroundColor": "LightGreen",
        "ButtonColor": "LightSalmon",
        "ButtonActionText": "Buy Now"
    }
}
// This is the dynamic content map that pairs with the Config Content.
[
  {
    "expression": "location = 'ch'",
    "name": "offer_title_test",
    "info": "Optional, additional info.",
    "groups": [
      {
        "variantName": "variant_a",
        "mod": [
          [
            0,
            199
          ]
        ],
        "values": {
          "LK-OfferTitle": "Extra Special Offer"
        }
      },
      {
        "variantName": "variant_b",
        "mod": [
          [
            200,
            499
          ]
        ],
        "values": {
          "LK-OfferTitle": "Offer Just For You!"
        }
      }
    ]
  }
]
// This is the body of the request to the Joystick REST API.
{
  "u": "d",
  "p":
  {
    "location": "ch"
  }
}
// This is the response back over REST API
{
  "data":
  {
    "SpecialPricingOffer":
    {
      "OfferTitle": "Extra Special Offer",
      "PercentBonus": "10",
      "Price": "4.99",
      "BackgroundColor": "LightGreen",
      "ButtonColor": "LightSalmon",
      "ButtonActionText": "Buy Now"
    }
  },
  "hash": "e35b02af",
  "meta":
  {
    "uid": 655955059,
    "mod": 59,
    "seg":
    [
      {
        "n": "offer_title_test",
        "i": "Optional, additional info.",
        "v": "variant_a"
      }
    ]
  }
}