美文网首页
JSONNET 入门

JSONNET 入门

作者: 国服最坑开发 | 来源:发表于2022-07-13 14:03 被阅读0次
jsonnet

0x00 TL;DR

jsonnet 是个啥?

jsonnet是google开源的代码模版化工具, 扩展了JSON,支持OO.
可以通过编写jsonnet脚本, 生成json/yaml的等配置文件, 应用场景: 生成k8s部署文件等.

本文通过记录官方指引的方式, 来学习jsonnet的一般用法.

0x01 jsonnet 语法

  • 首先, 一个合法的json文档,也是一个有效的jsonnet程序
  • jsonnet 中的变量(field) 不用引号
  • 数组里最后一个对象的后面, 要多加一个 逗号
  • 单引号, 双引号, 作用一样都表示字符串, 但是 双引号中的 单引号 可以不用转义.
  • |||块 可以表达多行字符串
  • @'foo', @"foo", 带@号的字符串, 只能表示单行字符串.
  • 可以使用 + 连接数据元素
/* A C-style comment. */
# A Python-style comment.
{
  cocktails: {
    // Ingredient quantities are in fl oz.
    "Tom C'ollins": {
      ingredients: [
        { kind: "Farmer's Gin", qty: 1.5 },
        { kind: 'Lemon', qty: 1 },
        { kind: 'Simple Syrup', qty: 0.5 },
        { kind: 'Soda', qty: 2 },
        { kind: 'Angostura', qty: 'dash' },
      ],
      garnish: 'Maraschino Cherry',
      served: 'Tall',
      description: |||
        The Tom Collins is essentially gin and
        lemonade.  The bitters add complexity.
        1212
      |||,
    },
    Manhattan: {
      ingredients: [
        { kind: 'Rye', qty: 2.5 },
        { kind: 'Sweet Red Vermouth', qty: 1 },
        { kind: 'Angostura', qty: 'dash' },
      ],
      garnish: 'Maraschino Cherry',
      served: 'Straight Up',
      description: @'A clear \ red drink.',
    },
  },
}

0x02 变量

  • 使用local关键字声明一个变量
  • 如果变量定义后面有其他字段, 则需要在声明后面加 逗号, 否则加 分号
// A regular definition.
local house_rum = 'Banks Rum';

{
  // A definition next to fields.
  local pour = 1.5,

  Daiquiri: {
    ingredients: [
      { kind: house_rum, qty: pour },
      { kind: 'Lime', qty: 1 },
      { kind: 'Simple Syrup', qty: 0.5 },
    ],
    served: 'Straight Up',
  },
  Mojito: {
    ingredients: [
      {
        kind: 'Mint',
        action: 'muddle',
        qty: 6,
        unit: 'leaves',
      },
      { kind: house_rum, qty: pour },
      { kind: 'Lime', qty: 0.5 },
      { kind: 'Simple Syrup', qty: 0.5 },
      { kind: 'Soda', qty: 3 },
    ],
    garnish: 'Lime wedge',
    served: 'Over crushed ice',
  },
}

0x03 使用变量

  • self 指向当前对象
  • $ 指向最外层对象
  • ['foo'] 查找一个字段
  • .f 查找一个对象下面的指定字段
  • [10] 查找一个数组对象元素
  • 允许任意长路径, 即: 可以 a.b.c 一直往下取
  • 数组切片 , arr[10:20:2], 同 python
  • 字符串支持unicode 查找/分隔
{
  'Tom Collins': {
    ingredients: [
      { kind: "Farmer's Gin", qty: 1.5 },
      { kind: 'Lemon', qty: 1 },
      { kind: 'Simple Syrup', qty: 0.5 },
      { kind: 'Soda', qty: 2 },
      { kind: 'Angostura', qty: 'dash' },
    ],
    garnish: 'Maraschino Cherry',
    served: 'Tall',
  },
  Martini: {
    ingredients: [
      {
        // Use the same gin as the Tom Collins.
        kind:
          $['Tom Collins'].ingredients[0].kind,
        qty: 2,
      },
      { kind: 'Dry White Vermouth', qty: 1 },
    ],
    garnish: 'Olive',
    served: 'Straight Up',
  },
  // Create an alias.
  'Gin Martini': self.Martini,
}

0x04 计算表达式

  • 支持浮点运算,位运算,逻辑运算
  • 和字符串进行+运算时, 会对其他非字符串对象进行隐式转换
  • 字符串支持比较大小
  • 对象支持 + 运算
  • 支持in 方法来判断一个对象是否包含字段
  • == 判断 两个对象所有字段都相等
  • 支持% 操作像python一样处理字符串格式化
{
  haha: 'a' in self,
  test2: '姓名:%s ,身高:%.2f米' % ['James', 2.35],
  concat_array: [1, 2, 3] + [4],
  concat_string: '123' + 4,
  equality1: 1 == '1',
  equality2: [{}, { x: 3 - 1 }]
             == [{}, { x: 2 }],
  ex1: 1 + 2 * 3 / (4 + 5),
  // Bitwise operations first cast to int.
  ex2: self.ex1 | 3,
  // Modulo operator.
  ex3: self.ex1 % 2,
  // Boolean logic
  ex4: (4 > 3) && (1 <= 3) || false,
  // Mixing objects together
  obj: { a: 1, b: 2 } + { b: 3, c: 4 },
  // Test if a field is in an object
  obj_member: 'foo' in { foo: 1 },
  // String formatting
  str1: 'The value of self.ex2 is '
        + self.ex2 + '.',
  str2: 'The value of self.ex2 is %g.'
        % self.ex2,
  str3: 'ex1=%0.2f, ex2=%0.2f'
        % [self.ex1, self.ex2],
  // By passing self, we allow ex1 and ex2 to
  // be extracted internally.
  str4: 'ex1=%(ex1)0.2f, ex2=%(ex2)0.2f'
        % self,
  // Do textual templating of entire files:
  str5: |||
    ex1=%(ex1)0.2f
    ex2=%(ex2)0.2f
  ||| % self,
}

0x05 方法

和python一样, 方法参数可以省略,支持默认值, 还有闭包.

// Define a local function.
// Default arguments are like Python:
local my_function(x, y=10) = x + y;

// Define a local multiline function.
local multiline_function(x) =
  // One can nest locals.
  local temp = x * 2;
  // Every local ends with a semi-colon.
  [temp, temp + 1];

local object = {
  // A method
  my_method(x): x * x,
};

{
  // Functions are first class citizens.
  call_inline_function:
    (function(x) x * x)(5),

  call_multiline_function: multiline_function(4),

  // Using the variable fetches the function,
  // the parens call the function.
  call: my_function(2),

  // Like python, parameters can be named at
  // call time.
  named_params: my_function(x=2),
  // This allows changing their order
  named_params2: my_function(y=3, x=2),

  // object.my_method returns the function,
  // which is then called like any other.
  call_method1: object.my_method(3),

  standard_lib:
    std.join(' ', std.split('foo/bar', '/')),
  len: [
    std.length('hello'),
    std.length([1, 2, 3]),
  ],
}

0x06 条件计算

if a then b else c
不写else的话, 返回 null

local Mojito(virgin=false, large=false) = {
  // A local next to fields ends with ','.
  local factor = if large then 2 else 1,
  // The ingredients are split into 3 arrays,
  // the middle one is either length 1 or 0.
  ingredients: [
    {
      kind: 'Mint',
      action: 'muddle',
      qty: 6 * factor,
      unit: 'leaves',
    },
  ] + (
    if virgin then [] else [
      { kind: 'Banks', qty: 1.5 * factor },
    ]
  ) + [
    { kind: 'Lime', qty: 0.5 * factor },
    { kind: 'Simple Syrup', qty: 0.5 * factor },
    { kind: 'Soda', qty: 3 * factor },
  ],
  // Returns null if not large.
  garnish: if large then 'Lime wedge',
  served: 'Over crushed ice',
};

{
  Mojito: Mojito(),
  'Virgin Mojito': Mojito(virgin=true),
  'Large Mojito': Mojito(large=true),
}

0x07 计算属性

Jsonnet objects can be used like a std::map or similar datastructures from regular languages.

  • Recall that a field lookup can be computed with obj[e]
  • The definition equivalent is {[e]: ... }
  • self or object locals cannot be accessed when field names are being computed, since the object is not yet constructed.
  • If a field name evaluates to null during object construction, the field is omitted. This works nicely with the default false branch of a conditional (see below).
local Margarita(salted) = {
  ingredients: [
    { kind: 'Tequila Blanco', qty: 2 },
    { kind: 'Lime', qty: 1 },
    { kind: 'Cointreau', qty: 1 },
  ],
  [if salted then 'garnish']: 'Salt',
};
{
  Margarita: Margarita(true),
  'Margarita Unsalted': Margarita(false),

}

0x08 数组对象表达式

在运行期, 给数组/对象添加元素/字段时, 可以使用 for in, 或 if 语法.

local arr = std.range(5, 8);
{
  array_comprehensions: {
    higher: [x + 3 for x in arr],
    lower: [x - 3 for x in arr],
    evens: [x for x in arr if x % 2 == 0],
    odds: [x for x in arr if x % 2 == 1],
    evens_and_odds: [
      '%d-%d' % [x, y]
      for x in arr
      if x % 2 == 0
      for y in arr
      if y % 2 == 1
    ],
  },
  object_comprehensions: {
    evens: {
      ['f' + x]: true
      for x in arr
      if x % 2 == 0
    },
    // Use object composition (+) to add in
    // static fields:
    mixture: {
      f: 1,
      g: 2,
    } + {
      [x]: 0
      for x in ['a', 'b', 'c']
    },
  },
}

0x09 引用

可以从其他文件引用代码 或 原始数据

  • import效果就是 复制被引用的代码
  • 被引用的文件后缀应该是: .libsonnet
  • 原始的json 也可以被引用
  • 可以使用 importstr 引用 UTF-8 文本
local martinis = import 'martinis.libsonnet';

{
  'Vodka Martini': martinis['Vodka Martini'],
  Manhattan: {
    ingredients: [
      { kind: 'Rye', qty: 2.5 },
      { kind: 'Sweet Red Vermouth', qty: 1 },
      { kind: 'Angostura', qty: 'dash' },
    ],
    garnish: importstr 'garnish.txt',
    served: 'Straight Up',
  },
}

下面是一个引用方法的例子:

  • utils.libsonnet
{
  equal_parts(size, ingredients)::
    // Define a function-scoped variable.
    local qty = size / std.length(ingredients);
    // Return an array.
    [
      { kind: i, qty: qty }
      for i in ingredients
    ],
}
  • negroni.jsonnet
local utils = import 'utils.libsonnet';
{
  Negroni: {
    // Divide 3oz among the 3 ingredients.
    ingredients: utils.equal_parts(3, [
      'Farmers Gin',
      'Sweet Red Vermouth',
      'Campari',
    ]),
    garnish: 'Orange Peel',
    served: 'On The Rocks',
  },
}

0x10 异常

Errors can arise from the language itself (e.g. an array overrun) or thrown from Jsonnet code. Stack traces provide context for the error.

  • To raise an error: error "foo"
  • To assert a condition before an expression: assert "foo";
  • A custom failure message: assert "foo" : "message";
  • Assert fields have a property: assert self.f == 10,
  • With custom failure message: assert "foo" : "message",

Try modifying the code below to trigger the assertion failures, and observe the error messages and stack traces that result.

You might wonder why the divide-by-zero is not triggered in the equal_parts function, since that code occurs before the std.length() check. Jsonnet is a lazy language, so variable initializers are not evaluated until the variable is used. Evaluation order is very hard to notice. It only becomes relevant when code throws errors, or takes a long time to execute. For more discussion of laziness, see design rationale.

0x11 全局参数配置

有两种方法可用于 全局参数配置

  • 外部变量
  • 顶层编码

外部变量

  • 传入变量:
jsonnet --ext-str prefix="Happy Hour "   --ext-code brunch=true ...
  • 代码中读取变量:
local lib = import 'library-ext.libsonnet';
{
  [std.extVar('prefix') + 'Pina Colada']: {
    ingredients: [
      { kind: 'Rum', qty: 3 },
      { kind: 'Pineapple Juice', qty: 6 },
      { kind: 'Coconut Cream', qty: 2 },
      { kind: 'Ice', qty: 12 },
    ],
    garnish: 'Pineapple slice',
    served: 'Frozen',
  },

  [if std.extVar('brunch') then
    std.extVar('prefix') + 'Bloody Mary'
  ]: {
    ingredients: [
      { kind: 'Vodka', qty: 1.5 },
      { kind: 'Tomato Juice', qty: 3 },
      { kind: 'Lemon Juice', qty: 1.5 },
      { kind: 'Worcestershire', qty: 0.25 },
      { kind: 'Tobasco Sauce', qty: 0.15 },
    ],
    garnish: 'Celery salt & pepper',
    served: 'Tall',
  },

  [std.extVar('prefix') + 'Mimosa']:
    lib.Mimosa,
}

顶层变量

  • 应该能被各个子模块引用
  • 应该提供默认值
  • 应该以库的方式提供出来

{
  // Note that the Mimosa is now
  // parameterized.
  Mimosa(brunch): {
    local fizz = if brunch then
      'Cheap Sparkling Wine'
    else
      'Champagne',
    ingredients: [
      { kind: fizz, qty: 3 },
      { kind: 'Orange Juice', qty: 3 },
    ],
    garnish: 'Orange Slice',
    served: 'Champagne Flute',
  },
}

使用

local lib = import 'library-tla.libsonnet';

// Here is the top-level function, note brunch
// now has a default value.
function(prefix, brunch=false) {

  [prefix + 'Pina Colada']: {
    ingredients: [
      { kind: 'Rum', qty: 3 },
      { kind: 'Pineapple Juice', qty: 6 },
      { kind: 'Coconut Cream', qty: 2 },
      { kind: 'Ice', qty: 12 },
    ],
    garnish: 'Pineapple slice',
    served: 'Frozen',
  },

  [if brunch then prefix + 'Bloody Mary']: {
    ingredients: [
      { kind: 'Vodka', qty: 1.5 },
      { kind: 'Tomato Juice', qty: 3 },
      { kind: 'Lemon Juice', qty: 1.5 },
      { kind: 'Worcestershire', qty: 0.25 },
      { kind: 'Tobasco Sauce', qty: 0.15 },
    ],
    garnish: 'Celery salt & pepper',
    served: 'Tall',
  },

  [prefix + 'Mimosa']: lib.Mimosa(brunch),
}

0x12 面向对象

先说几个特性:

  • 所有对象继承自 JSON
  • 对象都可以用 + 符号合并, 如果有字段冲突, 以右边的对象字段为准
  • self 指向当前对象

附加一些有趣的特性:

  • 隐藏字段使用::作前缀, 就不会输出到json/yaml了.
  • 支持super关键字,用于访问父类字段
  • 使用 +: 语法覆盖原来的对象字段值.
local Base = {
  f: 2,
  g: self.f + 100,
};

local WrapperBase = {
  Base: Base,
};

{
  Derived: Base + {
    f: 5,
    old_f: super.f,
    old_g: super.g,
  },
  WrapperDerived: WrapperBase + {
    Base+: { f: 5 },
  },
}

相关文章

网友评论

      本文标题:JSONNET 入门

      本文链接:https://www.haomeiwen.com/subject/yvizbrtx.html