v/vlib/x/json2
Delyan Angelov 1c0eefae38
parser/scanner: replace p.peek_tok2/3 with p.peek_token(2/3) (#8946)
2021-02-24 21:03:53 +03:00
..
README.md docs/readmes: format almost all remaining code blocks (#8590) 2021-02-05 19:50:28 +02:00
any_test.v json2: decode fn returns `?T`; add new tests (#6933) 2020-11-29 14:54:45 +01:00
decoder.v parser/scanner: replace p.peek_tok2/3 with p.peek_token(2/3) (#8946) 2021-02-24 21:03:53 +03:00
decoder_test.v json2: decode fn returns `?T`; add new tests (#6933) 2020-11-29 14:54:45 +01:00
encoder.v all: add strings.Builder.write_string and use write_string instead of write (#8892) 2021-02-22 13:18:11 +02:00
json2.v all: update copyright to 2019-2021 (#8029) 2021-01-18 13:20:06 +01:00
json2_test.v tests: fix some warnings in preparation for `-W test-self` 2021-01-31 11:05:17 +02:00

README.md

The name json2 was chosen to avoid any unwanted potential conflicts with the existing codegen tailored for the main json module which is powered by CJSON.

x.json2 is an experimental JSON parser written from scratch on V.

Usage

import x.json2
import net.http

fn main() {
	// Decoding
	resp := http.get('https://example.com') ?

	// raw decode
	raw_person := json2.raw_decode(resp.text) ?

	// Casting `Any` type / Navigating
	person := raw_person.as_map()
	name := person['name'].str() // Bob
	age := person['age'].int() // 19
	pi := person['pi'].f64() // 3.14....

	// Constructing an `Any` type
	mut me := map[string]json2.Any{}
	me['name'] = 'Bob'
	me['age'] = 18

	mut arr := []json2.Any{}
	arr << 'rock'
	arr << 'papers'
	arr << json2.null
	arr << 12

	me['interests'] = arr

	mut pets := map[string]json2.Any{}
	pets['Sam'] = 'Maltese Shitzu'
	me['pets'] = pets

	// Stringify to JSON
	println(me.str())
	//{
	//   "name":"Bob",
	//   "age":18,
	//   "interests":["rock","papers","scissors",null,12],
	//   "pets":{"Sam":"Maltese"}
	//}

	// Encode a struct/type to JSON
	encoded_json := json2.encode<Person>(person2)
}

Using decode<T> and encode<T>

Codegen for this feature is still WIP. You need to manually define the methods before using the module to structs.

In order to use the decode<T> and encode<T> function, you need to explicitly define two methods: from_json and to_json. from_json accepts a json2.Any argument and inside of it you need to map the fields you're going to put into the type. As for to_json method, you just need to map the values into json2.Any and turn it into a string.

struct Person {
mut:
    name string
    age  int = 20
    pets []string
}

fn (mut p Person) from_json(f json2.Any) {
    obj := f.as_map()
    for k, v in obj {
        match k {
            'name' { p.name = v.str() }
            'age' { p.age = v.int() }
            'pets' { p.pets = v.arr().map(it.str()) }
            else {}
        }
    }
}

fn (p Person) to_json() string {
    mut obj := map[string]json2.Any
    obj['name'] = p.name
    obj['age'] = p.age
    obj['pets'] = p.pets
    return obj.str()
}

fn main() {
    resp := os.read_file('./person.json')?
    person := json2.decode<Person>(resp)?
    println(person) // Person{name: 'Bob', age: 28, pets: ['Floof']}
    person_json := json2.encode<Person>(person)
    println(person_json) // {"name": "Bob", "age": 28, "pets": ["Floof"]}
}

Using struct tags

x.json2 can access and use the struct field tags similar to the json module by using the comp-time $for for structs.

fn (mut p Person) from_json(f json2.Any) {
    mp := an.as_map()
	mut js_field_name := ''
    $for field in Person.fields {
        js_field_name = field.name

        for attr in field.attrs {
			if attr.starts_with('json:') {
				js_field_name = attr.all_after('json:').trim_left(' ')
				break
			}
		}

        match field.name {
            'name' { p.name = mp[js_field_name].str() }
			'age' { u.age = mp[js_field_name].int() }
			'pets' { u.pets = mp[js_field_name].arr().map(it.str()) }
			else {}
		}
    }
}

Null Values

x.json2 has a separate null type for differentiating an undefined value and a null value. To verify that the field you're accessing is a null, use <typ> is json2.Null.

fn (mut p Person) from_json(f json2.Any) {
    obj := f.as_map()
    if obj['age'] is json2.Null {
        // use a default value
        p.age = 10
    }
}

Custom field names

Aside from using struct tags, you can also just simply cast the base field into a map (as_map()) and access the field you wish to put into the struct/type.

fn (mut p Person) from_json(f json2.Any) {
    obj := f.as_map()
    p.name = obj['nickname'].str()
}
fn (mut p Person) to_json() string {
	obj := f.as_map()
	obj['nickname'] = p.name
	return obj.str()
}

Undefined Values

Getting undefined values has the same behavior as regular V types. If you're casting a base field into map[string]json2.Any and fetch an undefined entry/value, it simply returns empty. As for the []json2.Any, it returns an index error.

Casting a value to an incompatible type

x.json2 provides methods for turning Any types into usable types. The following list shows the possible outputs when casting a value to an incompatible type.

  1. Casting non-array values as array (arr()) will return an array with the value as the content.
  2. Casting non-map values as map (as_map()) will return a map with the value as the content.
  3. Casting non-string values to string (str()) will return the JSON string representation of the value.
  4. Casting non-numeric values to int/float (int()/i64()/f32()/f64()) will return zero.